Import Cobalt 22.lts.1.304063
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id
index f5670fb..6446170 100644
--- a/src/cobalt/build/build.id
+++ b/src/cobalt/build/build.id
@@ -1 +1 @@
-303694
\ No newline at end of file
+304063
\ No newline at end of file
diff --git a/src/cobalt/build/save_build_id.py b/src/cobalt/build/save_build_id.py
index 014e80d..fc81ab9 100755
--- a/src/cobalt/build/save_build_id.py
+++ b/src/cobalt/build/save_build_id.py
@@ -63,7 +63,12 @@
     return 0
 
   if not options.build_id:
-    options.build_id = gyp_utils.GetBuildNumber()
+    build_id_server_url = os.environ.get('BUILD_ID_SERVER_URL')
+    if build_id_server_url:
+      options.build_id = gyp_utils.GetBuildNumber(
+          version_server=build_id_server_url)
+    else:
+      options.build_id = gyp_utils.GetBuildNumber()
 
   if not options.build_id:
     logging.error('Unable to retrieve build id.')
diff --git a/src/cobalt/content/fonts/BUILD.gn b/src/cobalt/content/fonts/BUILD.gn
index 87b11ff..8b99258 100644
--- a/src/cobalt/content/fonts/BUILD.gn
+++ b/src/cobalt/content/fonts/BUILD.gn
@@ -30,7 +30,7 @@
              "-i",
              rebase_path(font_xml, root_build_dir),
              "-o",
-             outputs[0],
+             rebase_path(outputs[0], root_out_dir),
            ] + package_categories
   }
 
diff --git a/src/cobalt/dom/performance.cc b/src/cobalt/dom/performance.cc
index 7554005..8154cc1 100644
--- a/src/cobalt/dom/performance.cc
+++ b/src/cobalt/dom/performance.cc
@@ -76,8 +76,6 @@
       tick_clock_(base::DefaultTickClock::GetInstance()),
       timing_(new PerformanceTiming(clock, time_origin_)),
       memory_(new MemoryInfo()),
-      lifecycle_timing_(
-          new PerformanceLifecycleTiming("lifecycle timing", time_origin_)),
       resource_timing_buffer_size_limit_(
           Performance::kMaxResourceTimingBufferSize),
       resource_timing_buffer_current_size_(0),
@@ -87,6 +85,8 @@
       add_to_performance_entry_buffer_flag_(false) {
   unix_at_zero_monotonic_ = GetUnixAtZeroMonotonic(
       base::DefaultClock::GetInstance(), tick_clock_);
+  lifecycle_timing_ = base::MakeRefCounted<PerformanceLifecycleTiming>(
+      "lifecycle timing", time_origin());
   QueuePerformanceEntry(lifecycle_timing_);
 }
 
diff --git a/src/cobalt/dom/performance_high_resolution_time.h b/src/cobalt/dom/performance_high_resolution_time.h
index 5033e5b..0684e41 100644
--- a/src/cobalt/dom/performance_high_resolution_time.h
+++ b/src/cobalt/dom/performance_high_resolution_time.h
@@ -39,7 +39,7 @@
   return (time - base::TimeTicks()).InMillisecondsF();
 }
 
-// Clamp customized minimum clock resolution.
+// Clamp customized minimum clock resolution in milliseconds.
 //   https://w3c.github.io/hr-time/#clock-resolution
 inline DOMHighResTimeStamp ClampTimeStampMinimumResolution(
     base::TimeTicks ticks,
@@ -49,13 +49,13 @@
         (microseconds % min_resolution_in_microseconds)).InMillisecondsF();
 }
 
-// Clamp customized minimum clock resolution.
+// Clamp customized minimum clock resolution in milliseconds.
 //   https://w3c.github.io/hr-time/#clock-resolution
 inline DOMHighResTimeStamp ClampTimeStampMinimumResolution(base::TimeDelta delta,
     int64_t min_resolution_in_microseconds) {
     int64_t microseconds = delta.InMicroseconds();
     return base::TimeDelta::FromMicroseconds(microseconds -
-        (microseconds % min_resolution_in_microseconds)).InMicrosecondsF();
+        (microseconds % min_resolution_in_microseconds)).InMillisecondsF();
 }
 
 }  // namespace dom
diff --git a/src/cobalt/dom/performance_lifecycle_timing.cc b/src/cobalt/dom/performance_lifecycle_timing.cc
index 08f47d4..f09fee0 100644
--- a/src/cobalt/dom/performance_lifecycle_timing.cc
+++ b/src/cobalt/dom/performance_lifecycle_timing.cc
@@ -41,23 +41,22 @@
   }
 
     DOMHighResTimeStamp ConvertSbTimeMonotonicToDOMHiResTimeStamp(
-      base::TimeTicks time_origin, SbTimeMonotonic monotonic_time) {
+      const DOMHighResTimeStamp time_origin, SbTimeMonotonic monotonic_time) {
     SbTimeMonotonic time_delta = SbTimeGetNow() - SbTimeGetMonotonicNow();
     base::Time base_time = base::Time::FromSbTime(time_delta + monotonic_time);
     base::TimeTicks time_ticks =
         base::TimeTicks::FromInternalValue(static_cast<int64_t>(
             base_time.ToJsTime()));
-    return Performance::MonotonicTimeToDOMHighResTimeStamp(
-        time_origin, time_ticks);
+    return ClampTimeStampMinimumResolution(time_ticks,
+        Performance::kPerformanceTimerMinResolutionInMicroseconds) - time_origin;
   }
 
 }  // namespace
 
 PerformanceLifecycleTiming::PerformanceLifecycleTiming(
-    const std::string& name, base::TimeTicks time_origin)
+    const std::string& name, const DOMHighResTimeStamp time_origin)
     : PerformanceEntry(name, 0.0, 0.0), time_origin_(time_origin) {}
 
-
 DOMHighResTimeStamp PerformanceLifecycleTiming::app_preload() const {
   return ReportDOMHighResTimeStamp(lifecycle_timing_info_.app_preload);
 }
diff --git a/src/cobalt/dom/performance_lifecycle_timing.h b/src/cobalt/dom/performance_lifecycle_timing.h
index 9baa10c..01e3c79 100644
--- a/src/cobalt/dom/performance_lifecycle_timing.h
+++ b/src/cobalt/dom/performance_lifecycle_timing.h
@@ -31,7 +31,7 @@
 class PerformanceLifecycleTiming : public PerformanceEntry {
  public:
   PerformanceLifecycleTiming(const std::string& name,
-                             base::TimeTicks time_origin);
+                             const DOMHighResTimeStamp time_origin);
 
   // Web API.
   DOMHighResTimeStamp app_preload() const;
@@ -83,7 +83,7 @@
 
   LifecycleTimingInfo lifecycle_timing_info_;
 
-  base::TimeTicks time_origin_;
+  DOMHighResTimeStamp time_origin_;
 
   DISALLOW_COPY_AND_ASSIGN(PerformanceLifecycleTiming);
 };
diff --git a/src/cobalt/loader/image/image_decoder.cc b/src/cobalt/loader/image/image_decoder.cc
index a499c63..68782a3 100644
--- a/src/cobalt/loader/image/image_decoder.cc
+++ b/src/cobalt/loader/image/image_decoder.cc
@@ -54,25 +54,6 @@
   result->append(message);
 }
 
-// Determine the ImageType of an image from its signature.
-ImageDecoder::ImageType DetermineImageType(const uint8* header) {
-  if (!memcmp(header, "\xFF\xD8\xFF", 3)) {
-    return ImageDecoder::kImageTypeJPEG;
-  } else if (!memcmp(header, "GIF87a", 6) || !memcmp(header, "GIF89a", 6)) {
-    return ImageDecoder::kImageTypeGIF;
-  } else if (!memcmp(header, "{", 1)) {
-    // TODO: Improve heuristics for determining whether the file contains valid
-    // Lottie JSON.
-    return ImageDecoder::kImageTypeJSON;
-  } else if (!memcmp(header, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8)) {
-    return ImageDecoder::kImageTypePNG;
-  } else if (!memcmp(header, "RIFF", 4) && !memcmp(header + 8, "WEBPVP", 6)) {
-    return ImageDecoder::kImageTypeWebP;
-  } else {
-    return ImageDecoder::kImageTypeInvalid;
-  }
-}
-
 // Returns true if the ResourceProvider is ResourceProviderStub.
 bool IsResourceProviderStub(render_tree::ResourceProvider* resource_provider) {
   return resource_provider->GetTypeId() ==
@@ -81,6 +62,39 @@
 
 }  // namespace
 
+// Determine the ImageType of an image from its signature.
+ImageDecoder::ImageType DetermineImageType(const uint8* header) {
+  if (!memcmp(header, "\xFF\xD8\xFF", 3)) {
+    return ImageDecoder::kImageTypeJPEG;
+  } else if (!memcmp(header, "GIF87a", 6) || !memcmp(header, "GIF89a", 6)) {
+    return ImageDecoder::kImageTypeGIF;
+  } else if (!memcmp(header, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8)) {
+    return ImageDecoder::kImageTypePNG;
+  } else if (!memcmp(header, "RIFF", 4) && !memcmp(header + 8, "WEBPVP", 6)) {
+    return ImageDecoder::kImageTypeWebP;
+  } else {
+    const std::string header_str = reinterpret_cast<const char*>(header);
+    std::string::size_type first_non_white_space_pos =
+        header_str.find_first_not_of(" \t\r\n");
+    if (first_non_white_space_pos + 1 < header_str.size()) {
+      if (header_str[first_non_white_space_pos] == '{' ||
+          header_str[first_non_white_space_pos] ==
+              '[') {  // json can start with either object hash or an array
+        std::string::size_type second_non_white_space_pos =
+            header_str.find_first_not_of(" \t\r\n",
+                                         first_non_white_space_pos + 1);
+        if (second_non_white_space_pos + 1 < header_str.size()) {
+          if (header_str[second_non_white_space_pos] == '"' &&
+              isalnum(header_str[second_non_white_space_pos + 1])) {
+            return ImageDecoder::kImageTypeJSON;
+          }
+        }
+      }
+    }
+    return ImageDecoder::kImageTypeInvalid;
+  }
+}
+
 ImageDecoder::ImageDecoder(
     render_tree::ResourceProvider* resource_provider,
     const base::DebuggerHooks& debugger_hooks,
diff --git a/src/cobalt/loader/image/image_decoder.h b/src/cobalt/loader/image/image_decoder.h
index cb80ce6..89b4d6b 100644
--- a/src/cobalt/loader/image/image_decoder.h
+++ b/src/cobalt/loader/image/image_decoder.h
@@ -122,6 +122,8 @@
   bool use_failure_image_decoder_ = false;
 };
 
+ImageDecoder::ImageType DetermineImageType(const uint8* header);
+
 }  // namespace image
 }  // namespace loader
 }  // namespace cobalt
diff --git a/src/cobalt/loader/image/image_decoder_unit_test.cc b/src/cobalt/loader/image/image_decoder_unit_test.cc
new file mode 100644
index 0000000..a6dfc5d
--- /dev/null
+++ b/src/cobalt/loader/image/image_decoder_unit_test.cc
@@ -0,0 +1,68 @@
+// Copyright 2021 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/loader/image/image_decoder.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cobalt {
+namespace loader {
+namespace image {
+
+TEST(ImageDecoderUnitTest, ValidImageType) {
+  EXPECT_EQ(DetermineImageType(reinterpret_cast<const uint8*>("\xFF\xD8\xFF")),
+            ImageDecoder::kImageTypeJPEG);
+  EXPECT_EQ(DetermineImageType(reinterpret_cast<const uint8*>("GIF87a")),
+            ImageDecoder::kImageTypeGIF);
+  EXPECT_EQ(DetermineImageType(reinterpret_cast<const uint8*>("GIF89a")),
+            ImageDecoder::kImageTypeGIF);
+  EXPECT_EQ(DetermineImageType(reinterpret_cast<const uint8*>(
+                "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A")),
+            ImageDecoder::kImageTypePNG);
+  EXPECT_EQ(
+      DetermineImageType(reinterpret_cast<const uint8*>("RIFF    WEBPVP")),
+      ImageDecoder::kImageTypeWebP);
+  EXPECT_EQ(
+      DetermineImageType(reinterpret_cast<const uint8*>("{\"v\":\"4.6.8\"")),
+      ImageDecoder::kImageTypeJSON);
+  EXPECT_EQ(DetermineImageType(
+                reinterpret_cast<const uint8*>("  {\t\"v\":\"4.6.8\"")),
+            ImageDecoder::kImageTypeJSON);
+  EXPECT_EQ(DetermineImageType(
+                reinterpret_cast<const uint8*>("\r [ \"v\":\"4.6.8\"")),
+            ImageDecoder::kImageTypeJSON);
+}
+
+TEST(ImageDecoderUnitTest, InvalidImageType) {
+  EXPECT_EQ(DetermineImageType(reinterpret_cast<const uint8*>(
+                "\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01"
+                "\x00")),
+            ImageDecoder::kImageTypeInvalid);
+  EXPECT_EQ(
+      DetermineImageType(reinterpret_cast<const uint8*>("{}\0\0\0\0\0\0")),
+      ImageDecoder::kImageTypeInvalid);
+  EXPECT_EQ(
+      DetermineImageType(reinterpret_cast<const uint8*>("{a\0\0\0\0\0\0")),
+      ImageDecoder::kImageTypeInvalid);
+  EXPECT_EQ(
+      DetermineImageType(reinterpret_cast<const uint8*>("{\"\"\0\0\0\0\0")),
+      ImageDecoder::kImageTypeInvalid);
+  EXPECT_EQ(
+      DetermineImageType(reinterpret_cast<const uint8*>("{\"{\0\0\0\0\0")),
+      ImageDecoder::kImageTypeInvalid);
+}
+
+}  // namespace image
+}  // namespace loader
+}  // namespace cobalt
diff --git a/src/cobalt/loader/loader.gyp b/src/cobalt/loader/loader.gyp
index 3a213f3..8f14dfd 100644
--- a/src/cobalt/loader/loader.gyp
+++ b/src/cobalt/loader/loader.gyp
@@ -142,6 +142,7 @@
         'file_fetcher_test.cc',
         'font/typeface_decoder_test.cc',
         'image/image_decoder_test.cc',
+        'image/image_decoder_unit_test.cc',
         'mesh/mesh_decoder_test.cc',
         'loader_test.cc',
         'text_decoder_test.cc',
diff --git a/src/cobalt/media_session/media_session_client.cc b/src/cobalt/media_session/media_session_client.cc
index 1a18649..99097ef 100644
--- a/src/cobalt/media_session/media_session_client.cc
+++ b/src/cobalt/media_session/media_session_client.cc
@@ -33,6 +33,9 @@
 // Delay to re-query position state after an action has been invoked.
 const base::TimeDelta kUpdateDelay = base::TimeDelta::FromMilliseconds(250);
 
+// Delay to check if the media session state is not active.
+const base::TimeDelta kMaybeFreezeDelay = base::TimeDelta::FromMilliseconds(1500);
+
 // Guess the media position state for the media session.
 void GuessMediaPositionState(MediaSessionState* session_state,
                              const media::WebMediaPlayer** guess_player,
@@ -67,7 +70,8 @@
 
 MediaSessionClient::MediaSessionClient(MediaSession* media_session)
     : media_session_(media_session),
-      platform_playback_state_(kMediaSessionPlaybackStateNone) {
+      platform_playback_state_(kMediaSessionPlaybackStateNone),
+      sequence_number_(0) {
   extension_ = static_cast<const CobaltExtensionMediaSessionApi*>(
       SbSystemGetExtension(kCobaltExtensionMediaSessionName));
   if (extension_) {
@@ -186,6 +190,17 @@
     UpdateMediaSessionState();
   }
 
+  if (!is_active()) {
+    media_session_->task_runner_->PostDelayedTask(
+        FROM_HERE, base::Bind(&MediaSessionClient::RunMaybeFreezeCallback,
+                              base::Unretained(this), ++sequence_number_),
+                              kMaybeFreezeDelay);
+  }
+}
+
+void MediaSessionClient::RunMaybeFreezeCallback(int sequence_number) {
+  if (sequence_number != sequence_number_) return;
+
   if (!is_active() && !maybe_freeze_callback_.is_null()) {
     maybe_freeze_callback_.Run();
   }
diff --git a/src/cobalt/media_session/media_session_client.h b/src/cobalt/media_session/media_session_client.h
index 3e37d1f..115b852 100644
--- a/src/cobalt/media_session/media_session_client.h
+++ b/src/cobalt/media_session/media_session_client.h
@@ -95,6 +95,10 @@
     media_session_ = media_session;
   }
 
+  // If the media session is not active, then run MaybeFreezeCallback to
+  // suspend the App.
+  void RunMaybeFreezeCallback(int sequence_number);
+
  private:
   THREAD_CHECKER(thread_checker_);
   MediaSession* media_session_;
@@ -133,6 +137,10 @@
 
   base::Closure maybe_freeze_callback_;
 
+  // This is for checking the sequence number of PostDelayedTask. It should be
+  // aligned with a single thread.
+  int sequence_number_;
+
   DISALLOW_COPY_AND_ASSIGN(MediaSessionClient);
 };
 
diff --git a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
index a9305ef..076aaf8 100644
--- a/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
+++ b/src/cobalt/renderer/rasterizer/egl/textured_mesh_renderer.cc
@@ -20,6 +20,7 @@
 #include <string>
 #include <vector>
 
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "cobalt/extension/graphics.h"
 #include "cobalt/math/size.h"
@@ -364,28 +365,31 @@
       graphics_extension->GetMapToMeshColorAdjustments(&color_adjustment)) {
     // Setup vectors for the color adjustments. Ensure they use dot for decimal
     // point regardless of locale.
-    std::stringstream buffer;
-    buffer.imbue(std::locale::classic());
-    buffer << "  vec4 scale0 = vec4(" << color_adjustment.rgba0_scale[0] << ","
-           << color_adjustment.rgba0_scale[1] << ","
-           << color_adjustment.rgba0_scale[2] << ","
-           << color_adjustment.rgba0_scale[3] << ");";
-    buffer << "  vec4 scale1 = vec4(" << color_adjustment.rgba1_scale[0] << ","
-           << color_adjustment.rgba1_scale[1] << ","
-           << color_adjustment.rgba1_scale[2] << ","
-           << color_adjustment.rgba1_scale[3] << ");";
-    buffer << "  vec4 scale2 = vec4(" << color_adjustment.rgba2_scale[0] << ","
-           << color_adjustment.rgba2_scale[1] << ","
-           << color_adjustment.rgba2_scale[2] << ","
-           << color_adjustment.rgba2_scale[3] << ");";
-    buffer << "  vec4 scale3 = vec4(" << color_adjustment.rgba3_scale[0] << ","
-           << color_adjustment.rgba3_scale[1] << ","
-           << color_adjustment.rgba3_scale[2] << ","
-           << color_adjustment.rgba3_scale[3] << ");";
+    std::string buffer;
+    buffer = "  vec4 scale0 = vec4(" +
+             base::NumberToString(color_adjustment.rgba0_scale[0]) + "," +
+             base::NumberToString(color_adjustment.rgba0_scale[1]) + "," +
+             base::NumberToString(color_adjustment.rgba0_scale[2]) + "," +
+             base::NumberToString(color_adjustment.rgba0_scale[3]) + ");" +
+             "  vec4 scale1 = vec4(" +
+             base::NumberToString(color_adjustment.rgba1_scale[0]) + "," +
+             base::NumberToString(color_adjustment.rgba1_scale[1]) + "," +
+             base::NumberToString(color_adjustment.rgba1_scale[2]) + "," +
+             base::NumberToString(color_adjustment.rgba1_scale[3]) + ");" +
+             "  vec4 scale2 = vec4(" +
+             base::NumberToString(color_adjustment.rgba2_scale[0]) + "," +
+             base::NumberToString(color_adjustment.rgba2_scale[1]) + "," +
+             base::NumberToString(color_adjustment.rgba2_scale[2]) + "," +
+             base::NumberToString(color_adjustment.rgba2_scale[3]) + ");" +
+             "  vec4 scale3 = vec4(" +
+             base::NumberToString(color_adjustment.rgba3_scale[0]) + "," +
+             base::NumberToString(color_adjustment.rgba3_scale[1]) + "," +
+             base::NumberToString(color_adjustment.rgba3_scale[2]) + "," +
+             base::NumberToString(color_adjustment.rgba3_scale[3]) + ");";
     blit_fragment_shader_source +=
         "  vec4 color2 = color * color;"
         "  vec4 color3 = color2 * color;" +
-        buffer.str() +
+        buffer +
         "  color = scale0 + scale1*color + scale2*color2 + scale3*color3;"
         "  gl_FragColor = clamp(color, vec4(0.0), vec4(1.0));"
         "}";
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/skia.gyp b/src/cobalt/renderer/rasterizer/skia/skia/skia.gyp
index dfefca6..5fe1b23 100644
--- a/src/cobalt/renderer/rasterizer/skia/skia/skia.gyp
+++ b/src/cobalt/renderer/rasterizer/skia/skia/skia.gyp
@@ -28,6 +28,7 @@
       'target_name': 'skia_library',
       'type': 'static_library',
       'dependencies': [
+        '<(DEPTH)/base/base.gyp:base',
         'skia_library_no_asan',
       ],
       'includes': [
@@ -65,7 +66,6 @@
       'target_name': 'filter_fuzz_stub',
       'type': 'executable',
       'dependencies': [
-        '<(DEPTH)/base/base.gyp:base',
         'skia',
       ],
       'sources': [
diff --git a/src/cobalt/site/docs/development/setup-android.md b/src/cobalt/site/docs/development/setup-android.md
index 61849d8..f999d81 100644
--- a/src/cobalt/site/docs/development/setup-android.md
+++ b/src/cobalt/site/docs/development/setup-android.md
@@ -9,7 +9,7 @@
 ## Preliminary Setup
 
 <aside class="note">
-<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.md">"Set up your environment - Linux"</a>. Complete the section **Set up your workstation**, then return and complete the following steps.
+<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the section **Set up your workstation**, then return and complete the following steps.
 </aside>
 
 1.  Additional build dependencies may need to be installed:
@@ -224,7 +224,9 @@
 run that on a device, it needs to be packaged into an APK, which is done by the
 associated "deploy" target (e.g. nplb_deploy). The Starboard test runner does
 all this for you, so just use that to build and run tests. For example, to
-build and run "devel" NPLB on an ARM64 device, from the top 'src' directory:
+build and run "devel" NPLB on an ARM64 device, from the top 'src' directory
+(if you've unnested the 'src' directory, just run this from your top-level
+directory):
 
 ```
 starboard/tools/testing/test_runner.py -p android-arm64 -c devel -b -r -t nplb
diff --git a/src/cobalt/site/docs/development/setup-linux.md b/src/cobalt/site/docs/development/setup-linux.md
index 6b9df7e..9e40a3e 100644
--- a/src/cobalt/site/docs/development/setup-linux.md
+++ b/src/cobalt/site/docs/development/setup-linux.md
@@ -55,10 +55,29 @@
     $ git clone https://cobalt.googlesource.com/cobalt
     ```
 
+### Set up Developer Tools
+
+Cobalt's developer tools require a different file structure which we are in the
+process of moving to. For now, if you want to use these tools, you must unnest
+the `src/` directory like so:
+
+```
+$ cd cobalt
+$ git mv src/* ./
+$ git mv src/.* ./
+```
+
+Once you do that, you'll be able to follow the following two steps to have C++
+and Python linting and formatting as well as other helpful checks enabled. Keep
+in mind that after doing this, you'll want to run following commands in the
+top-level directory instead of the `src/` subdirectory.
+
+Git will track this as a large change, we recommend that you create a commit
+for it and rebase that commit of our upstream continuously for now.
+
 1.  Create a Python 3 virtual environment for working on Cobalt (feel free to use `virtualenvwrapper` instead):
 
     ```
-    $ cd cobalt
     $ python -m venv ~/.virtualenvs/cobalt_dev
     $ source ~/.virtualenvs/cobalt_dev
     $ pip install -r requirements.txt
diff --git a/src/cobalt/site/docs/development/setup-raspi.md b/src/cobalt/site/docs/development/setup-raspi.md
index 75d43cc..0e58a7d 100644
--- a/src/cobalt/site/docs/development/setup-raspi.md
+++ b/src/cobalt/site/docs/development/setup-raspi.md
@@ -54,7 +54,7 @@
 ## Set up your workstation
 
 <aside class="note">
-<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.md">"Set up your environment - Linux"</a>. Complete the section **Set up your workstation**, then return and complete the following steps.
+<b>Note:</b> Before proceeding further, refer to the documentation for <a href="setup-linux.html">"Set up your environment - Linux"</a>. Complete the section **Set up your workstation**, then return and complete the following steps.
 </aside>
 
 The following steps install the cross-compiling toolchain on your workstation.
@@ -106,6 +106,11 @@
 
 ## Build, install, and run Cobalt for Raspberry Pi
 
+<aside class="note">
+<b>Note:</b> If you've unnested the 'src' directory, build and compile the code
+in your top-level checkout of Cobalt instead of the 'src' subdirectory.
+</aside>
+
 1.  Build the code by navigating to the src directory in your cobalt directory and run the
     following command :
 
diff --git a/src/cobalt/site/docs/reference/starboard/modules/13/media.md b/src/cobalt/site/docs/reference/starboard/modules/13/media.md
index 9b4d7bb..6b46874 100644
--- a/src/cobalt/site/docs/reference/starboard/modules/13/media.md
+++ b/src/cobalt/site/docs/reference/starboard/modules/13/media.md
@@ -682,24 +682,6 @@
 bool SbMediaIsBufferUsingMemoryPool()
 ```
 
-### SbMediaIsSupported ###
-
-Indicates whether this platform supports decoding `video_codec` and
-`audio_codec` along with decrypting using `key_system`. If `video_codec` is
-`kSbMediaVideoCodecNone` or if `audio_codec` is `kSbMediaAudioCodecNone`, this
-function should return `true` as long as `key_system` is supported on the
-platform to decode any supported input formats.
-
-`video_codec`: The `SbMediaVideoCodec` being checked for platform compatibility.
-`audio_codec`: The `SbMediaAudioCodec` being checked for platform compatibility.
-`key_system`: The key system being checked for platform compatibility.
-
-#### Declaration ####
-
-```
-bool SbMediaIsSupported(SbMediaVideoCodec video_codec, SbMediaAudioCodec audio_codec, const char *key_system)
-```
-
 ### SbMediaSetAudioWriteDuration ###
 
 Communicate to the platform how far past `current_playback_position` the app
diff --git a/src/cobalt/site/docs/reference/starboard/modules/media.md b/src/cobalt/site/docs/reference/starboard/modules/media.md
index 4ad1e16..6b46874 100644
--- a/src/cobalt/site/docs/reference/starboard/modules/media.md
+++ b/src/cobalt/site/docs/reference/starboard/modules/media.md
@@ -682,12 +682,6 @@
 bool SbMediaIsBufferUsingMemoryPool()
 ```
 
-#### Declaration ####
-
-```
-bool SbMediaIsSupported(SbMediaVideoCodec video_codec, SbMediaAudioCodec audio_codec, const char *key_system)
-```
-
 ### SbMediaSetAudioWriteDuration ###
 
 Communicate to the platform how far past `current_playback_position` the app
diff --git a/src/cobalt/updater/network_fetcher.cc b/src/cobalt/updater/network_fetcher.cc
index fb7ea98..1dfbc7c 100644
--- a/src/cobalt/updater/network_fetcher.cc
+++ b/src/cobalt/updater/network_fetcher.cc
@@ -129,6 +129,13 @@
   url_fetcher_->Start();
 }
 
+void NetworkFetcher::CancelDownloadToFile() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+  SB_LOG(INFO) << "Canceling DownloadToFile";
+  url_fetcher_.reset();
+}
+
 void NetworkFetcher::OnURLFetchResponseStarted(const net::URLFetcher* source) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   std::move(response_started_callback_)
diff --git a/src/cobalt/updater/network_fetcher.h b/src/cobalt/updater/network_fetcher.h
index 1ff2675..17b8909 100644
--- a/src/cobalt/updater/network_fetcher.h
+++ b/src/cobalt/updater/network_fetcher.h
@@ -69,6 +69,7 @@
                       ProgressCallback progress_callback,
                       DownloadToFileCompleteCallback
                           download_to_file_complete_callback) override;
+  void CancelDownloadToFile() override;
 
   // net::URLFetcherDelegate interface.
   void OnURLFetchResponseStarted(const net::URLFetcher* source) override;
diff --git a/src/cobalt/updater/updater_module.cc b/src/cobalt/updater/updater_module.cc
index 8f5663f..3941cb6 100644
--- a/src/cobalt/updater/updater_module.cc
+++ b/src/cobalt/updater/updater_module.cc
@@ -174,6 +174,7 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   update_client_->RemoveObserver(updater_observer_.get());
   updater_observer_.reset();
+  update_client_->Stop();
   update_client_ = nullptr;
 
   if (updater_configurator_ != nullptr) {
diff --git a/src/components/update_client/component.cc b/src/components/update_client/component.cc
index 787ec2e..421dc40 100644
--- a/src/components/update_client/component.cc
+++ b/src/components/update_client/component.cc
@@ -300,6 +300,13 @@
       base::BindOnce(&Component::ChangeState, base::Unretained(this)));
 }
 
+#if defined(STARBOARD)
+void Component::Cancel() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  state_->Cancel();
+}
+#endif
+
 void Component::ChangeState(std::unique_ptr<State> next_state) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
@@ -532,6 +539,14 @@
   DoHandle();
 }
 
+#if defined(STARBOARD)
+void Component::State::Cancel() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  // Further work may be needed to ensure cancelation during any state results
+  // in a clear result and no memory leaks.
+}
+#endif
+
 void Component::State::TransitionState(std::unique_ptr<State> next_state) {
   DCHECK(thread_checker_.CalledOnValidThread());
   DCHECK(next_state);
@@ -726,6 +741,13 @@
   DCHECK(thread_checker_.CalledOnValidThread());
 }
 
+#if defined(STARBOARD)
+void Component::StateDownloadingDiff::Cancel() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  crx_downloader_->CancelDownload();
+}
+#endif
+
 void Component::StateDownloadingDiff::DoHandle() {
   DCHECK(thread_checker_.CalledOnValidThread());
 
@@ -800,6 +822,13 @@
   DCHECK(thread_checker_.CalledOnValidThread());
 }
 
+#if defined(STARBOARD)
+void Component::StateDownloading::Cancel() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  crx_downloader_->CancelDownload();
+}
+#endif
+
 void Component::StateDownloading::DoHandle() {
   DCHECK(thread_checker_.CalledOnValidThread());
 
diff --git a/src/components/update_client/component.h b/src/components/update_client/component.h
index 4058985..f6cc035 100644
--- a/src/components/update_client/component.h
+++ b/src/components/update_client/component.h
@@ -55,6 +55,12 @@
   // to the next component state before |callback_handle_complete_| is invoked.
   void Handle(CallbackHandleComplete callback_handle_complete);
 
+#if defined(STARBOARD)
+  // Stops update progress for the component and may clean resources used in its
+  // current state.
+  void Cancel();
+#endif
+
   CrxUpdateItem GetCrxUpdateItem() const;
 
   // Sets the uninstall state for this component.
@@ -156,6 +162,11 @@
     // by the outer component, after the current state is fully handled.
     void Handle(CallbackNextState callback);
 
+#if defined(STARBOARD)
+    // Stops update progress and may clean resources used in the current state.
+    virtual void Cancel();
+#endif
+
     ComponentState state() const { return state_; }
 
    protected:
@@ -247,6 +258,9 @@
    public:
     explicit StateDownloadingDiff(Component* component);
     ~StateDownloadingDiff() override;
+#if defined(STARBOARD)
+    void Cancel() override;
+#endif
 
    private:
     // State overrides.
@@ -270,6 +284,9 @@
    public:
     explicit StateDownloading(Component* component);
     ~StateDownloading() override;
+#if defined(STARBOARD)
+    void Cancel() override;
+#endif
 
    private:
     // State overrides.
diff --git a/src/components/update_client/crx_downloader.cc b/src/components/update_client/crx_downloader.cc
index fe83b57..8f2bffa 100644
--- a/src/components/update_client/crx_downloader.cc
+++ b/src/components/update_client/crx_downloader.cc
@@ -120,6 +120,12 @@
   DoStartDownload(*current_url_);
 }
 
+#if defined(STARBOARD)
+void CrxDownloader::CancelDownload() {
+  DoCancelDownload();
+}
+#endif
+
 void CrxDownloader::OnDownloadComplete(
     bool is_handled,
     const Result& result,
diff --git a/src/components/update_client/crx_downloader.h b/src/components/update_client/crx_downloader.h
index 6378d03..ae19a4a 100644
--- a/src/components/update_client/crx_downloader.h
+++ b/src/components/update_client/crx_downloader.h
@@ -122,6 +122,10 @@
                      const std::string& expected_hash,
                      DownloadCallback download_callback);
 
+#if defined(STARBOARD)
+  void CancelDownload();
+#endif
+
   const std::vector<DownloadMetrics> download_metrics() const;
 
  protected:
@@ -151,6 +155,9 @@
 
  private:
   virtual void DoStartDownload(const GURL& url) = 0;
+#if defined(STARBOARD)
+  virtual void DoCancelDownload() = 0;
+#endif
 
   void VerifyResponse(bool is_handled,
                       Result result,
diff --git a/src/components/update_client/network.h b/src/components/update_client/network.h
index daed162..dec108f 100644
--- a/src/components/update_client/network.h
+++ b/src/components/update_client/network.h
@@ -63,6 +63,9 @@
       ResponseStartedCallback response_started_callback,
       ProgressCallback progress_callback,
       DownloadToFileCompleteCallback download_to_file_complete_callback) = 0;
+#if defined(STARBOARD)
+  virtual void CancelDownloadToFile() = 0;
+#endif
 
  protected:
   NetworkFetcher() = default;
diff --git a/src/components/update_client/task_update.cc b/src/components/update_client/task_update.cc
index ab445b5..0129e4c 100644
--- a/src/components/update_client/task_update.cc
+++ b/src/components/update_client/task_update.cc
@@ -36,13 +36,25 @@
     return;
   }
 
+#if defined(STARBOARD)
+  update_engine_->Update(is_foreground_, ids_, std::move(crx_data_callback_),
+                         base::BindOnce(&TaskUpdate::TaskComplete, this),
+                         cancelation_closure_);
+#else
   update_engine_->Update(is_foreground_, ids_, std::move(crx_data_callback_),
                          base::BindOnce(&TaskUpdate::TaskComplete, this));
+#endif
 }
 
 void TaskUpdate::Cancel() {
   DCHECK(thread_checker_.CalledOnValidThread());
 
+#if defined(STARBOARD)
+  if (cancelation_closure_) {  // The engine's picked up the task.
+    std::move(cancelation_closure_).Run();
+  }
+#endif
+
   TaskComplete(Error::UPDATE_CANCELED);
 }
 
diff --git a/src/components/update_client/task_update.h b/src/components/update_client/task_update.h
index 4df796b..74c0a6a 100644
--- a/src/components/update_client/task_update.h
+++ b/src/components/update_client/task_update.h
@@ -57,6 +57,9 @@
   const std::vector<std::string> ids_;
   UpdateClient::CrxDataCallback crx_data_callback_;
   Callback callback_;
+#if defined(STARBOARD)
+  base::OnceClosure cancelation_closure_;
+#endif
 
   DISALLOW_COPY_AND_ASSIGN(TaskUpdate);
 };
diff --git a/src/components/update_client/update_client.cc b/src/components/update_client/update_client.cc
index 369c6d3..6ce65bf 100644
--- a/src/components/update_client/update_client.cc
+++ b/src/components/update_client/update_client.cc
@@ -201,6 +201,16 @@
 
   is_stopped_ = true;
 
+  // Cancel the pending tasks. These tasks are safe to cancel and delete since
+  // they have not picked up by the update engine, and not shared with any
+  // task runner yet.
+  while (!task_queue_.empty()) {
+    auto task = task_queue_.front();
+    task_queue_.pop_front();
+    task->Cancel();
+  }
+
+#if !defined(STARBOARD)
   // In the current implementation it is sufficient to cancel the pending
   // tasks only. The tasks that are run by the update engine will stop
   // making progress naturally, as the main task runner stops running task
@@ -210,15 +220,15 @@
   // area, to cancel the running tasks by canceling the current action update.
   // This behavior would be expected, correct, and result in no resource leaks
   // in all cases, in shutdown or not.
-  //
-  // Cancel the pending tasks. These tasks are safe to cancel and delete since
-  // they have not picked up by the update engine, and not shared with any
-  // task runner yet.
-  while (!task_queue_.empty()) {
-    auto task = task_queue_.front();
-    task_queue_.pop_front();
+#else
+  // For Cobalt it's not sufficient to just let the tasks already picked up by
+  // the update engine stop naturally, as this can result in resource leaks and
+  // crashes. These tasks are also canceled so that any necessary cleanup can be
+  // done.
+  for (auto task : tasks_) {
     task->Cancel();
   }
+#endif
 }
 
 void UpdateClientImpl::SendUninstallPing(const std::string& id,
diff --git a/src/components/update_client/update_engine.cc b/src/components/update_client/update_engine.cc
index a6d6920..29c260b 100644
--- a/src/components/update_client/update_engine.cc
+++ b/src/components/update_client/update_engine.cc
@@ -72,10 +72,19 @@
   DCHECK(thread_checker_.CalledOnValidThread());
 }
 
+#if !defined(STARBOARD)
 void UpdateEngine::Update(bool is_foreground,
                           const std::vector<std::string>& ids,
                           UpdateClient::CrxDataCallback crx_data_callback,
                           Callback callback) {
+#else
+void UpdateEngine::Update(bool is_foreground,
+                          const std::vector<std::string>& ids,
+                          UpdateClient::CrxDataCallback crx_data_callback,
+                          Callback callback,
+                          base::OnceClosure& cancelation_closure) {
+
+#endif
   DCHECK(thread_checker_.CalledOnValidThread());
 
   if (ids.empty()) {
@@ -100,6 +109,10 @@
       config_, is_foreground, ids, std::move(crx_data_callback),
       notify_observers_callback_, std::move(callback), crx_downloader_factory_);
   DCHECK(!update_context->session_id.empty());
+#if defined(STARBOARD)
+  cancelation_closure = base::BindOnce(&UpdateEngine::Cancel, this,
+                                       update_context->session_id, ids);
+#endif
 
   const auto result = update_contexts_.insert(
       std::make_pair(update_context->session_id, update_context));
@@ -405,6 +418,17 @@
          now < throttle_updates_until_;
 }
 
+#if defined(STARBOARD)
+void UpdateEngine::Cancel(const std::string& update_context_session_id,
+                          const std::vector<std::string>& crx_component_ids) {
+  const auto& context = update_contexts_.at(update_context_session_id);
+  for (const auto& crx_component_id : crx_component_ids) {
+    auto& component = context->components.at(crx_component_id);
+    component->Cancel();
+  }
+}
+#endif
+
 void UpdateEngine::SendUninstallPing(const std::string& id,
                                      const base::Version& version,
                                      int reason,
diff --git a/src/components/update_client/update_engine.h b/src/components/update_client/update_engine.h
index 9601873..4384fa8 100644
--- a/src/components/update_client/update_engine.h
+++ b/src/components/update_client/update_engine.h
@@ -56,10 +56,20 @@
   // is not found.
   bool GetUpdateState(const std::string& id, CrxUpdateItem* update_state);
 
+#if !defined(STARBOARD)
   void Update(bool is_foreground,
               const std::vector<std::string>& ids,
               UpdateClient::CrxDataCallback crx_data_callback,
               Callback update_callback);
+#else
+  // |cancelation_closure| is populated with a closure that can be run to cancel
+  // the update requested by the caller.
+  void Update(bool is_foreground,
+              const std::vector<std::string>& ids,
+              UpdateClient::CrxDataCallback crx_data_callback,
+              Callback update_callback,
+              base::OnceClosure& cancelation_closure);
+#endif
 
   void SendUninstallPing(const std::string& id,
                          const base::Version& version,
@@ -96,6 +106,14 @@
   // occurs too soon.
   bool IsThrottled(bool is_foreground) const;
 
+#if defined(STARBOARD)
+  // Cancels updates currently handled by the engine for each component
+  // identified by one of |crx_component_ids| for the update context identified
+  // by the |update_context_session_id|.
+  void Cancel(const std::string& update_context_session_id,
+              const std::vector<std::string>& crx_component_ids);
+#endif
+
   base::ThreadChecker thread_checker_;
   scoped_refptr<Configurator> config_;
   UpdateChecker::Factory update_checker_factory_;
diff --git a/src/components/update_client/url_fetcher_downloader.cc b/src/components/update_client/url_fetcher_downloader.cc
index 83c5727..078c275 100644
--- a/src/components/update_client/url_fetcher_downloader.cc
+++ b/src/components/update_client/url_fetcher_downloader.cc
@@ -144,6 +144,13 @@
 #endif
 }
 
+#if defined(STARBOARD)
+void UrlFetcherDownloader::DoCancelDownload() {
+  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+  network_fetcher_->CancelDownloadToFile();
+}
+#endif
+
 void UrlFetcherDownloader::CreateDownloadDir() {
   base::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_url_fetcher_"),
                                &download_dir_);
diff --git a/src/components/update_client/url_fetcher_downloader.h b/src/components/update_client/url_fetcher_downloader.h
index e5c240d..3880efc 100644
--- a/src/components/update_client/url_fetcher_downloader.h
+++ b/src/components/update_client/url_fetcher_downloader.h
@@ -42,6 +42,9 @@
  private:
   // Overrides for CrxDownloader.
   void DoStartDownload(const GURL& url) override;
+#if defined(STARBOARD)
+  void DoCancelDownload() override;
+#endif
 
   void CreateDownloadDir();
   void StartURLFetch(const GURL& url);
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
index 0e1d66b..18418e8 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioOutputManager.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.media.AudioAttributes;
+import android.media.AudioDeviceCallback;
 import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 import android.media.AudioManager;
@@ -29,12 +30,16 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /** Creates and destroys AudioTrackBridge and handles the volume change. */
 public class AudioOutputManager implements CobaltMediaSession.UpdateVolumeListener {
   private List<AudioTrackBridge> audioTrackBridgeList;
   private Context context;
 
+  AtomicBoolean hasAudioDeviceChanged = new AtomicBoolean(false);
+  boolean hasRegisteredAudioDeviceCallback = false;
+
   public AudioOutputManager(Context context) {
     this.context = context;
     audioTrackBridgeList = new ArrayList<AudioTrackBridge>();
@@ -54,7 +59,7 @@
       int sampleRate,
       int channelCount,
       int preferredBufferSizeInBytes,
-      boolean enableAudioRouting,
+      boolean enableAudioDeviceCallback,
       int tunnelModeAudioSessionId) {
     AudioTrackBridge audioTrackBridge =
         new AudioTrackBridge(
@@ -62,13 +67,58 @@
             sampleRate,
             channelCount,
             preferredBufferSizeInBytes,
-            enableAudioRouting,
             tunnelModeAudioSessionId);
     if (!audioTrackBridge.isAudioTrackValid()) {
       Log.e(TAG, "AudioTrackBridge has invalid audio track");
       return null;
     }
     audioTrackBridgeList.add(audioTrackBridge);
+    hasAudioDeviceChanged.set(false);
+
+    if (Build.VERSION.SDK_INT < 23
+        || hasRegisteredAudioDeviceCallback
+        || !enableAudioDeviceCallback) {
+      return audioTrackBridge;
+    }
+
+    AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+    audioManager.registerAudioDeviceCallback(
+        new AudioDeviceCallback() {
+          // Since registering a callback triggers an immediate call to onAudioDevicesAdded() with
+          // current devices, don't set |hasAudioDeviceChanged| for this initial call.
+          private boolean initialDevicesAdded = false;
+
+          private void handleConnectedDeviceChange(AudioDeviceInfo[] devices) {
+            for (AudioDeviceInfo info : devices) {
+              // TODO: Determine if AudioDeviceInfo.TYPE_HDMI_EARC should be checked in API 31.
+              if (info.isSink()
+                  && (info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP
+                      || info.getType() == AudioDeviceInfo.TYPE_HDMI_ARC
+                      || info.getType() == AudioDeviceInfo.TYPE_HDMI)) {
+                // TODO: Avoid destroying the AudioTrack if the new devices can support the current
+                // AudioFormat.
+                hasAudioDeviceChanged.set(true);
+                break;
+              }
+            }
+          }
+
+          @Override
+          public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            if (initialDevicesAdded) {
+              handleConnectedDeviceChange(addedDevices);
+              return;
+            }
+            initialDevicesAdded = true;
+          }
+
+          @Override
+          public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+            handleConnectedDeviceChange(removedDevices);
+          }
+        },
+        null);
+    hasRegisteredAudioDeviceCallback = true;
     return audioTrackBridge;
   }
 
@@ -307,50 +357,95 @@
               encoding, Build.VERSION.SDK_INT));
       return false;
     }
-    if (hasPassthroughSupportForV23(encoding)) {
+
+    AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+    AudioDeviceInfo[] deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+
+    // Some devices have issues on reporting playback capability and managing routing when Bluetooth
+    // output is connected.  So e/ac3 support is disabled when Bluetooth output device is connected.
+    for (AudioDeviceInfo info : deviceInfos) {
+      if (info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) {
+        Log.i(
+            TAG,
+            String.format(
+                "Passthrough on encoding %d is disabled because Bluetooth output device is"
+                    + " connected.",
+                encoding));
+        return false;
+      }
+    }
+
+    // Sample rate is not provided when the function is called, assume it is 48000.
+    final int DEFAULT_SURROUND_SAMPLE_RATE = 48000;
+
+    if (hasPassthroughSupportForV23(deviceInfos, encoding)) {
       Log.i(
           TAG,
           String.format(
               "Passthrough on encoding %d is supported, as hasPassthroughSupportForV23() returns"
                   + " true.",
               encoding));
-      return true;
-    }
-    if (Build.VERSION.SDK_INT < 29) {
-      Log.i(
-          TAG,
-          String.format(
-              "Passthrough on encoding %d is rejected, as"
-                  + " hasDirectSurroundingPlaybackSupportForV29() is not called for api %d.",
-              encoding, Build.VERSION.SDK_INT));
-      return false;
-    }
-    if (hasDirectSurroundingPlaybackSupportForV29(encoding)) {
-      Log.i(
-          TAG,
-          String.format(
-              "Passthrough on encoding %d is supported, as"
-                  + " hasDirectSurroundingPlaybackSupportForV29() returns true.",
-              encoding));
-      return true;
+    } else {
+      if (Build.VERSION.SDK_INT < 29) {
+        Log.i(
+            TAG,
+            String.format(
+                "Passthrough on encoding %d is rejected, as"
+                    + " hasDirectSurroundPlaybackSupportForV29() is not called for api %d.",
+                encoding, Build.VERSION.SDK_INT));
+        return false;
+      }
+      if (hasDirectSurroundPlaybackSupportForV29(encoding, DEFAULT_SURROUND_SAMPLE_RATE)) {
+        Log.i(
+            TAG,
+            String.format(
+                "Passthrough on encoding %d is supported, as"
+                    + " hasDirectSurroundPlaybackSupportForV29() returns true.",
+                encoding));
+      } else {
+        Log.i(
+            TAG,
+            String.format(
+                "Passthrough on encoding %d is not supported, as"
+                    + " hasDirectSurroundPlaybackSupportForV29() returns false.",
+                encoding));
+        return false;
+      }
     }
 
-    Log.i(
-        TAG,
-        String.format(
-            "Passthrough on encoding %d is not supported, as"
-                + " hasDirectSurroundingPlaybackSupportForV29() returns false.",
-            encoding));
-    return false;
+    Log.i(TAG, "Verify passthrough support by creating an AudioTrack.");
+
+    try {
+      AudioTrack audioTrack =
+          new AudioTrack(
+              getDefaultAudioAttributes(),
+              getPassthroughAudioFormatFor(encoding, DEFAULT_SURROUND_SAMPLE_RATE),
+              AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_5POINT1, encoding),
+              AudioTrack.MODE_STREAM,
+              AudioManager.AUDIO_SESSION_ID_GENERATE);
+      audioTrack.release();
+    } catch (Exception e) {
+      // AudioTrack creation can fail if the audio is routed to an unexpected device. For example,
+      // when the user has Bluetooth headphones connected, or when the encoding is EAC3 and both
+      // HDMI and SPDIF are connected, where the output should fallback to AC3.
+      Log.w(
+          TAG,
+          String.format(
+              "Passthrough on encoding %d is disabled because creating AudioTrack raises"
+                  + " exception: ",
+              encoding),
+          e);
+      return false;
+    }
+
+    Log.i(TAG, "AudioTrack creation succeeded, passthrough support verified.");
+
+    return true;
   }
 
   /** Returns whether passthrough on `encoding` is supported for API 23 and above. */
   @RequiresApi(23)
-  private boolean hasPassthroughSupportForV23(int encoding) {
-    AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-    AudioDeviceInfo[] deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-
-    // TODO: Verify the follow code returns false if non-surrounding BT device is routed.
+  private boolean hasPassthroughSupportForV23(final AudioDeviceInfo[] deviceInfos, int encoding) {
     for (AudioDeviceInfo info : deviceInfos) {
       final int type = info.getType();
       if (type != AudioDeviceInfo.TYPE_HDMI && type != AudioDeviceInfo.TYPE_HDMI_ARC) {
@@ -394,39 +489,50 @@
   }
 
   @RequiresApi(29)
-  /**
-   * Returns whether direct playback on surrounding `encoding` is supported for API 29 and above.
-   */
-  private boolean hasDirectSurroundingPlaybackSupportForV29(int encoding) {
+  /** Returns whether direct playback on surround `encoding` is supported for API 29 and above. */
+  private boolean hasDirectSurroundPlaybackSupportForV29(int encoding, int sampleRate) {
     if (encoding != AudioFormat.ENCODING_AC3
         && encoding != AudioFormat.ENCODING_E_AC3
         && encoding != AudioFormat.ENCODING_E_AC3_JOC) {
       Log.w(
           TAG,
           String.format(
-              "hasDirectSurroundingPlaybackSupportForV29() encountered unsupported encoding %d.",
+              "hasDirectSurroundPlaybackSupportForV29() encountered unsupported encoding %d.",
               encoding));
       return false;
     }
 
-    // Sample rate is not provided when the function is called, assume it is 48000.
-    final int DEFAULT_SURROUNDING_SAMPLE_RATE = 48000;
-    AudioFormat format =
-        new AudioFormat.Builder()
-            .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
-            .setEncoding(encoding)
-            .setSampleRate(DEFAULT_SURROUNDING_SAMPLE_RATE)
-            .build();
-    AudioAttributes attributes =
-        new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_MEDIA)
-            .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
-            .build();
-    final boolean supported = AudioTrack.isDirectPlaybackSupported(format, attributes);
+    boolean supported =
+        AudioTrack.isDirectPlaybackSupported(
+            getPassthroughAudioFormatFor(encoding, sampleRate), getDefaultAudioAttributes());
     Log.i(
         TAG,
         String.format(
             "isDirectPlaybackSupported() for encoding %d returned %b.", encoding, supported));
     return supported;
   }
+
+  // TODO: Move utility functions into a separate class.
+  /** Returns AudioFormat for surround `encoding` and `sampleRate`. */
+  static AudioFormat getPassthroughAudioFormatFor(int encoding, int sampleRate) {
+    return new AudioFormat.Builder()
+        .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
+        .setEncoding(encoding)
+        .setSampleRate(sampleRate)
+        .build();
+  }
+
+  /** Returns default AudioAttributes for surround playbacks. */
+  static AudioAttributes getDefaultAudioAttributes() {
+    // TODO: Turn this into a static variable after it is moved into a separate class.
+    return new AudioAttributes.Builder()
+        .setUsage(AudioAttributes.USAGE_MEDIA)
+        .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
+        .build();
+  }
+
+  @UsedByNative
+  private boolean getAndResetHasAudioDeviceChanged() {
+    return hasAudioDeviceChanged.getAndSet(false);
+  }
 }
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
index 7441984..0001fde 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/AudioTrackBridge.java
@@ -17,11 +17,8 @@
 import static dev.cobalt.media.Log.TAG;
 
 import android.media.AudioAttributes;
-import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 import android.media.AudioManager;
-import android.media.AudioRouting;
-import android.media.AudioRouting.OnRoutingChangedListener;
 import android.media.AudioTimestamp;
 import android.media.AudioTrack;
 import android.os.Build;
@@ -30,8 +27,6 @@
 import dev.cobalt.util.UsedByNative;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
-import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * A wrapper of the android AudioTrack class. Android AudioTrack would not start playing until the
@@ -51,10 +46,6 @@
   private ByteBuffer avSyncHeader;
   private int avSyncPacketBytesRemaining;
 
-  private AtomicBoolean newAudioDeviceAdded = new AtomicBoolean(false);
-  private AudioDeviceInfo currentRoutedDevice;
-  private OnRoutingChangedListener onRoutingChangedListener;
-
   private static int getBytesPerSample(int audioFormat) {
     switch (audioFormat) {
       case AudioFormat.ENCODING_PCM_16BIT:
@@ -73,10 +64,7 @@
       int sampleRate,
       int channelCount,
       int preferredBufferSizeInBytes,
-      boolean enableAudioRouting,
       int tunnelModeAudioSessionId) {
-    // TODO: Re-enable audio routing when all related bugs are fixed.
-    enableAudioRouting = false;
 
     tunnelModeEnabled = tunnelModeAudioSessionId != -1;
     int channelConfig;
@@ -121,15 +109,15 @@
               .build();
     } else {
       // TODO: Support ENCODING_E_AC3_JOC for api level 28 or later.
-      final boolean is_surrounding =
+      final boolean is_surround =
           sampleType == AudioFormat.ENCODING_AC3 || sampleType == AudioFormat.ENCODING_E_AC3;
-      // TODO: We start to enforce |CONTENT_TYPE_MOVIE| for surrounding playback, investigate if we
-      //       can use |CONTENT_TYPE_MOVIE| for all non-surrounding AudioTrack used by video
+      // TODO: We start to enforce |CONTENT_TYPE_MOVIE| for surround playback, investigate if we
+      //       can use |CONTENT_TYPE_MOVIE| for all non-surround AudioTrack used by video
       //       playback.
       attributes =
           new AudioAttributes.Builder()
               .setContentType(
-                  is_surrounding
+                  is_surround
                       ? AudioAttributes.CONTENT_TYPE_MOVIE
                       : AudioAttributes.CONTENT_TYPE_MUSIC)
               .setUsage(AudioAttributes.USAGE_MEDIA)
@@ -176,31 +164,6 @@
             audioTrackBufferSize,
             preferredBufferSizeInBytes,
             AudioTrack.getMinBufferSize(sampleRate, channelConfig, sampleType)));
-    if (audioTrack != null && enableAudioRouting && Build.VERSION.SDK_INT >= 24) {
-      Log.i(TAG, "Audio routing enabled.");
-      currentRoutedDevice = audioTrack.getRoutedDevice();
-      onRoutingChangedListener =
-          new AudioRouting.OnRoutingChangedListener() {
-            @Override
-            public void onRoutingChanged(AudioRouting router) {
-              AudioDeviceInfo newRoutedDevice = router.getRoutedDevice();
-              if (currentRoutedDevice == null && newRoutedDevice != null) {
-                if (!areAudioDevicesEqual(currentRoutedDevice, newRoutedDevice)) {
-                  Log.v(
-                      TAG,
-                      String.format(
-                          "New audio device %s added to AudioTrackAudioSink.",
-                          newRoutedDevice.getProductName()));
-                  newAudioDeviceAdded.set(true);
-                }
-              }
-              currentRoutedDevice = newRoutedDevice;
-            }
-          };
-      audioTrack.addOnRoutingChangedListener(onRoutingChangedListener, null);
-    } else {
-      Log.i(TAG, "Audio routing disabled.");
-    }
   }
 
   public Boolean isAudioTrackValid() {
@@ -209,9 +172,6 @@
 
   public void release() {
     if (audioTrack != null) {
-      if (Build.VERSION.SDK_INT >= 24 && onRoutingChangedListener != null) {
-        audioTrack.removeOnRoutingChangedListener(onRoutingChangedListener);
-      }
       audioTrack.release();
     }
     audioTrack = null;
@@ -424,26 +384,4 @@
     }
     return audioTrack.getUnderrunCount();
   }
-
-  @RequiresApi(23)
-  private boolean areAudioDevicesEqual(AudioDeviceInfo device1, AudioDeviceInfo device2) {
-    if (device1.getId() == device2.getId()
-        && device1.getType() == device2.getType()
-        && device1.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
-      // This is a workaround for the emulator, which triggers an error callback when switching
-      // between devices with different hash codes that are otherwise identical.
-      return Arrays.equals(device1.getChannelCounts(), device2.getChannelCounts())
-          && Arrays.equals(device1.getChannelIndexMasks(), device2.getChannelIndexMasks())
-          && Arrays.equals(device1.getChannelMasks(), device2.getChannelMasks())
-          && Arrays.equals(device1.getEncodings(), device2.getEncodings())
-          && Arrays.equals(device1.getSampleRates(), device2.getSampleRates())
-          && device1.getProductName().equals(device2.getProductName());
-    }
-    return device1.equals(device2);
-  }
-
-  @UsedByNative
-  private boolean getAndResetHasNewAudioDeviceAdded() {
-    return newAudioDeviceAdded.getAndSet(false);
-  }
 }
diff --git a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
index 4bf638b..0ca198c 100644
--- a/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
+++ b/src/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
@@ -33,6 +33,7 @@
 import dev.cobalt.util.Log;
 import dev.cobalt.util.UsedByNative;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 
 /** A wrapper of the MediaCodec class. */
 @SuppressWarnings("unused")
@@ -364,9 +365,7 @@
 
       // This logic is inspired by
       // https://github.com/google/ExoPlayer/blob/deb9b301b2c7ef66fdd7d8a3e58298a79ba9c619/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java#L1803.
-      byte[] hdrStaticInfoData = new byte[25];
-      ByteBuffer hdrStaticInfo = ByteBuffer.wrap(hdrStaticInfoData);
-
+      ByteBuffer hdrStaticInfo = ByteBuffer.allocateDirect(25).order(ByteOrder.LITTLE_ENDIAN);
       hdrStaticInfo.put((byte) 0);
       hdrStaticInfo.putShort((short) ((primaryRChromaticityX * MAX_CHROMATICITY) + 0.5f));
       hdrStaticInfo.putShort((short) ((primaryRChromaticityY * MAX_CHROMATICITY) + 0.5f));
diff --git a/src/starboard/android/arm/platform_configuration/configuration.gni b/src/starboard/android/arm/platform_configuration/configuration.gni
index 92e7a77..a4cf9ff 100644
--- a/src/starboard/android/arm/platform_configuration/configuration.gni
+++ b/src/starboard/android/arm/platform_configuration/configuration.gni
@@ -15,4 +15,5 @@
 import("//starboard/android/shared/platform_configuration/configuration.gni")
 
 android_abi = "armeabi-v7a"
+arm_version = 7
 sabi_path = "//starboard/sabi/arm/softfp/sabi-v$sb_api_version.json"
diff --git a/src/starboard/android/arm64/platform_configuration/configuration.gni b/src/starboard/android/arm64/platform_configuration/configuration.gni
index 13ca22c..efade93 100644
--- a/src/starboard/android/arm64/platform_configuration/configuration.gni
+++ b/src/starboard/android/arm64/platform_configuration/configuration.gni
@@ -15,4 +15,5 @@
 import("//starboard/android/shared/platform_configuration/configuration.gni")
 
 android_abi = "arm64-v8a"
+arm_version = 8
 sabi_path = "//starboard/sabi/arm64/sabi-v$sb_api_version.json"
diff --git a/src/starboard/android/arm64/vulkan/platform_configuration/configuration.gni b/src/starboard/android/arm64/vulkan/platform_configuration/configuration.gni
index c6d6f73..b90be0f 100644
--- a/src/starboard/android/arm64/vulkan/platform_configuration/configuration.gni
+++ b/src/starboard/android/arm64/vulkan/platform_configuration/configuration.gni
@@ -15,6 +15,7 @@
 import("//starboard/android/shared/platform_configuration/configuration.gni")
 
 android_abi = "arm64-v8a"
+arm_version = 8
 sabi_path = "//starboard/sabi/arm64/sabi-v$sb_api_version.json"
 
 enable_vulkan = true
diff --git a/src/starboard/android/shared/audio_renderer_passthrough.cc b/src/starboard/android/shared/audio_renderer_passthrough.cc
index 4f2820d..1f0849c 100644
--- a/src/starboard/android/shared/audio_renderer_passthrough.cc
+++ b/src/starboard/android/shared/audio_renderer_passthrough.cc
@@ -34,8 +34,6 @@
 constexpr SbTime kAudioTrackUpdateInternal = kSbTimeMillisecond * 5;
 
 constexpr int kPreferredBufferSizeInBytes = 16 * 1024;
-// TODO: Enable audio routing and link it to client side experiment.
-constexpr bool kEnableAudioRouting = false;
 // TODO: Enable passthrough with tunnel mode.
 constexpr int kTunnelModeAudioSessionId = -1;
 
@@ -75,8 +73,10 @@
 
 AudioRendererPassthrough::AudioRendererPassthrough(
     const SbMediaAudioSampleInfo& audio_sample_info,
-    SbDrmSystem drm_system)
-    : audio_sample_info_(audio_sample_info) {
+    SbDrmSystem drm_system,
+    bool enable_audio_device_callback)
+    : audio_sample_info_(audio_sample_info),
+      enable_audio_device_callback_(enable_audio_device_callback) {
   SB_DCHECK(audio_sample_info_.codec == kSbMediaAudioCodecAc3 ||
             audio_sample_info_.codec == kSbMediaAudioCodecEac3);
   if (SbDrmSystemIsValid(drm_system)) {
@@ -384,7 +384,7 @@
       optional<SbMediaAudioSampleType>(),  // Not required in passthrough mode
       audio_sample_info_.number_of_channels,
       audio_sample_info_.samples_per_second, kPreferredBufferSizeInBytes,
-      kEnableAudioRouting, kTunnelModeAudioSessionId));
+      enable_audio_device_callback_, kTunnelModeAudioSessionId));
 
   if (!audio_track_bridge->is_valid()) {
     error_cb_(kSbPlayerErrorDecode, "Error creating AudioTrackBridge");
@@ -436,6 +436,16 @@
   SB_DCHECK(error_cb_);
   SB_DCHECK(audio_track_bridge_);
 
+  if (enable_audio_device_callback_ &&
+      audio_track_bridge_->GetAndResetHasAudioDeviceChanged()) {
+    SB_LOG(INFO) << "Audio device changed, raising a capability changed error "
+                    "to restart playback.";
+    error_cb_(kSbPlayerErrorCapabilityChanged,
+              "Audio device capability changed");
+    audio_track_bridge_->PauseAndFlush();
+    return;
+  }
+
   AudioTrackState current_state;
 
   {
@@ -499,15 +509,21 @@
           sample_buffer, samples_to_write, sync_time);
       // Error code returned as negative value, like kAudioTrackErrorDeadObject.
       if (samples_written < 0) {
-        // `kSbPlayerErrorDecode` is used for general SbPlayer error, there is
-        // no error code corresponding to audio sink.
-        auto error = kSbPlayerErrorDecode;
         if (samples_written == AudioTrackBridge::kAudioTrackErrorDeadObject) {
           // Inform the audio end point change.
-          error = kSbPlayerErrorCapabilityChanged;
+          SB_LOG(INFO)
+              << "Write error for dead audio track, audio device capability "
+                 "has likely changed. Restarting playback.";
+          error_cb_(kSbPlayerErrorCapabilityChanged,
+                    "Audio device capability changed");
+          audio_track_bridge_->PauseAndFlush();
+          return;
         }
-        error_cb_(error, FormatString("Error while writing frames: %d",
-                                      samples_written));
+        // `kSbPlayerErrorDecode` is used for general SbPlayer error, there is
+        // no error code corresponding to audio sink.
+        error_cb_(
+            kSbPlayerErrorDecode,
+            FormatString("Error while writing frames: %d", samples_written));
       }
       decoded_audio_writing_offset_ += samples_written;
 
diff --git a/src/starboard/android/shared/audio_renderer_passthrough.h b/src/starboard/android/shared/audio_renderer_passthrough.h
index 6d9a050..58a073c 100644
--- a/src/starboard/android/shared/audio_renderer_passthrough.h
+++ b/src/starboard/android/shared/audio_renderer_passthrough.h
@@ -49,7 +49,8 @@
       private ::starboard::shared::starboard::player::JobQueue::JobOwner {
  public:
   AudioRendererPassthrough(const SbMediaAudioSampleInfo& audio_sample_info,
-                           SbDrmSystem drm_system);
+                           SbDrmSystem drm_system,
+                           bool enable_audio_device_callback);
   ~AudioRendererPassthrough() override;
 
   bool is_valid() const { return decoder_ != nullptr; }
@@ -96,8 +97,9 @@
   void OnDecoderConsumed();
   void OnDecoderOutput();
 
-  // The following two variables are set in the ctor.
+  // The following three variables are set in the ctor.
   const SbMediaAudioSampleInfo audio_sample_info_;
+  const bool enable_audio_device_callback_;
   // The AudioDecoder is used as a decryptor when the stream is encrypted.
   // TODO: Revisit to encapsulate the AudioDecoder as a SbDrmSystemPrivate
   //       instead.  This would need to turn SbDrmSystemPrivate::Decrypt() into
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.cc b/src/starboard/android/shared/audio_track_audio_sink_type.cc
index 4c46710..57e9aed 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.cc
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -82,7 +82,7 @@
     SbAudioSinkPrivate::ErrorFunc error_func,
     SbTime start_time,
     int tunnel_mode_audio_session_id,
-    bool enable_audio_routing,
+    bool enable_audio_device_callback,
     void* context)
     : type_(type),
       channels_(channels),
@@ -105,7 +105,7 @@
               channels,
               sampling_frequency_hz,
               preferred_buffer_size_in_bytes,
-              enable_audio_routing,
+              enable_audio_device_callback,
               tunnel_mode_audio_session_id) {
   SB_DCHECK(update_source_status_func_);
   SB_DCHECK(consume_frames_func_);
@@ -178,10 +178,11 @@
   while (!quit_) {
     int playback_head_position = 0;
     SbTime frames_consumed_at = 0;
-    if (bridge_.GetAndResetHasNewAudioDeviceAdded(env)) {
-      SB_LOG(INFO) << "New audio device added.";
-      error_func_(kSbPlayerErrorCapabilityChanged, "New audio device added.",
-                  context_);
+    if (bridge_.GetAndResetHasAudioDeviceChanged(env)) {
+      SB_LOG(INFO) << "Audio device changed, raising a capability changed "
+                      "error to restart playback.";
+      error_func_(kSbPlayerErrorCapabilityChanged,
+                  "Audio device capability changed", context_);
       break;
     }
 
@@ -328,6 +329,7 @@
           capabilities_changed,
           FormatString("Error while writing frames: %d", written_frames),
           context_);
+      SB_LOG(INFO) << "Restarting playback.";
       break;
     } else if (written_frames > 0) {
       last_written_succeeded_at = now;
@@ -418,13 +420,15 @@
     SbAudioSinkPrivate::ErrorFunc error_func,
     void* context) {
   const SbTime kStartTime = 0;
-  const int kTunnelModeAudioSessionId = -1;  // disable tunnel mode
-  const bool kEnableAudioRouting = true;
+  // Disable tunnel mode.
+  const int kTunnelModeAudioSessionId = -1;
+  // Disable AudioDeviceCallback for WebAudio.
+  const bool kEnableAudioDeviceCallback = false;
   return Create(channels, sampling_frequency_hz, audio_sample_type,
                 audio_frame_storage_type, frame_buffers, frames_per_channel,
                 update_source_status_func, consume_frames_func, error_func,
-                kStartTime, kTunnelModeAudioSessionId, kEnableAudioRouting,
-                context);
+                kStartTime, kTunnelModeAudioSessionId,
+                kEnableAudioDeviceCallback, context);
 }
 
 SbAudioSink AudioTrackAudioSinkType::Create(
@@ -439,7 +443,7 @@
     SbAudioSinkPrivate::ErrorFunc error_func,
     SbTime start_media_time,
     int tunnel_mode_audio_session_id,
-    bool enable_audio_routing,
+    bool enable_audio_device_callback,
     void* context) {
   int min_required_frames = SbAudioSinkGetMinBufferSizeInFrames(
       channels, audio_sample_type, sampling_frequency_hz);
@@ -450,8 +454,8 @@
       this, channels, sampling_frequency_hz, audio_sample_type, frame_buffers,
       frames_per_channel, preferred_buffer_size_in_bytes,
       update_source_status_func, consume_frames_func, error_func,
-      start_media_time, tunnel_mode_audio_session_id, enable_audio_routing,
-      context);
+      start_media_time, tunnel_mode_audio_session_id,
+      enable_audio_device_callback, context);
   if (!audio_sink->IsAudioTrackValid()) {
     SB_DLOG(ERROR)
         << "AudioTrackAudioSinkType::Create failed to create audio track";
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.h b/src/starboard/android/shared/audio_track_audio_sink_type.h
index 5ae9f7b..adf050d 100644
--- a/src/starboard/android/shared/audio_track_audio_sink_type.h
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.h
@@ -68,7 +68,7 @@
       SbAudioSinkPrivate::ErrorFunc error_func,
       SbTime start_time,
       int tunnel_mode_audio_session_id,
-      bool enable_audio_routing,
+      bool enable_audio_device_callback,
       void* context);
 
   bool IsValid(SbAudioSink audio_sink) override {
@@ -111,7 +111,7 @@
       SbAudioSinkPrivate::ErrorFunc error_func,
       SbTime start_media_time,
       int tunnel_mode_audio_session_id,
-      bool enable_audio_routing,
+      bool enable_audio_device_callback,
       void* context);
   ~AudioTrackAudioSink() override;
 
diff --git a/src/starboard/android/shared/audio_track_bridge.cc b/src/starboard/android/shared/audio_track_bridge.cc
index 572e759..79fe83f 100644
--- a/src/starboard/android/shared/audio_track_bridge.cc
+++ b/src/starboard/android/shared/audio_track_bridge.cc
@@ -39,7 +39,7 @@
                                    int channels,
                                    int sampling_frequency_hz,
                                    int preferred_buffer_size_in_bytes,
-                                   bool enable_audio_routing,
+                                   bool enable_audio_device_callback,
                                    int tunnel_mode_audio_session_id) {
   if (coding_type == kSbMediaAudioCodingTypePcm) {
     SB_DCHECK(SbAudioSinkIsAudioSampleTypeSupported(sample_type.value()));
@@ -65,7 +65,7 @@
       j_audio_output_manager.Get(), "createAudioTrackBridge",
       "(IIIIZI)Ldev/cobalt/media/AudioTrackBridge;",
       GetAudioFormatSampleType(coding_type, sample_type), sampling_frequency_hz,
-      channels, preferred_buffer_size_in_bytes, enable_audio_routing,
+      channels, preferred_buffer_size_in_bytes, enable_audio_device_callback,
       tunnel_mode_audio_session_id);
   if (!j_audio_track_bridge) {
     // One of the cases that this may hit is when output happened to be switched
@@ -272,13 +272,17 @@
                                   "J");
 }
 
-bool AudioTrackBridge::GetAndResetHasNewAudioDeviceAdded(
+bool AudioTrackBridge::GetAndResetHasAudioDeviceChanged(
     JniEnvExt* env /*= JniEnvExt::Get()*/) {
   SB_DCHECK(env);
   SB_DCHECK(is_valid());
 
+  ScopedLocalJavaRef<jobject> j_audio_output_manager(
+      env->CallStarboardObjectMethodOrAbort(
+          "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
+
   return env->CallBooleanMethodOrAbort(
-      j_audio_track_bridge_, "getAndResetHasNewAudioDeviceAdded", "()Z");
+      j_audio_output_manager.Get(), "getAndResetHasAudioDeviceChanged", "()Z");
 }
 
 int AudioTrackBridge::GetUnderrunCount(JniEnvExt* env /*= JniEnvExt::Get()*/) {
diff --git a/src/starboard/android/shared/audio_track_bridge.h b/src/starboard/android/shared/audio_track_bridge.h
index e18e498..607837d 100644
--- a/src/starboard/android/shared/audio_track_bridge.h
+++ b/src/starboard/android/shared/audio_track_bridge.h
@@ -41,7 +41,7 @@
                    int channels,
                    int sampling_frequency_hz,
                    int preferred_buffer_size_in_bytes,
-                   bool enable_audio_routing,
+                   bool enable_audio_device_callback,
                    int tunnel_mode_audio_session_id);
   ~AudioTrackBridge();
 
@@ -79,7 +79,7 @@
   // updated on return.  It can be nullptr.
   int64_t GetPlaybackHeadPosition(SbTime* updated_at,
                                   JniEnvExt* env = JniEnvExt::Get());
-  bool GetAndResetHasNewAudioDeviceAdded(JniEnvExt* env = JniEnvExt::Get());
+  bool GetAndResetHasAudioDeviceChanged(JniEnvExt* env = JniEnvExt::Get());
   int GetUnderrunCount(JniEnvExt* env = JniEnvExt::Get());
 
  private:
diff --git a/src/starboard/android/shared/gyp_configuration.py b/src/starboard/android/shared/gyp_configuration.py
index e38efdf..018dbb6 100644
--- a/src/starboard/android/shared/gyp_configuration.py
+++ b/src/starboard/android/shared/gyp_configuration.py
@@ -277,52 +277,6 @@
   # A map of failing or crashing tests per target.
   __FILTERED_TESTS = {  # pylint: disable=invalid-name
       'player_filter_tests': [
-          # All e/ac3 related decoder tests are disabled.  They will be
-          # re-enabled soon once we can filter out audio decoder tests for
-          # passthrough decoders.
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/12',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/26',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/40',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/54',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/68',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/82',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/84',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/86',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/88',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/90',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/92',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/94',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/96',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/98',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/100',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/102',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.SingleInput/104',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/12',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/26',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/40',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/54',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/68',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/82',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/84',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/86',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/88',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/90',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/92',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/94',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/96',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.MultipleInput/98',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.' +
-              'MultipleInput/100',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.' +
-              'MultipleInput/102',
-          'AdaptiveAudioDecoderTests/AdaptiveAudioDecoderTest.' +
-              'MultipleInput/104',
-          'AudioDecoderTests/AudioDecoderTest.SingleInput/12',
-          'AudioDecoderTests/AudioDecoderTest.ResetBeforeInput/12',
-          'AudioDecoderTests/AudioDecoderTest.MultipleInputs/12',
-          'AudioDecoderTests/AudioDecoderTest.LimitedInput/12',
-          'AudioDecoderTests/AudioDecoderTest.ContinuedLimitedInput/12',
-
           # GetMaxNumberOfCachedFrames() on Android is device dependent,
           # and Android doesn't provide an API to get it. So, this function
           # doesn't make sense on Android. But HoldFramesUntilFull tests depend
diff --git a/src/starboard/android/shared/media_is_audio_supported.cc b/src/starboard/android/shared/media_is_audio_supported.cc
index 334c7dd..e7b04de 100644
--- a/src/starboard/android/shared/media_is_audio_supported.cc
+++ b/src/starboard/android/shared/media_is_audio_supported.cc
@@ -46,17 +46,17 @@
     return false;
   }
   MimeType mime_type(content_type);
-  // Allows for disabling the use of the AudioRouting API to detect when audio
-  // peripherals are connected. Enabled by default.
-  // (https://developer.android.com/reference/android/media/AudioRouting)
-  auto enable_audio_routing_parameter_value =
-      mime_type.GetParamStringValue("enableaudiorouting", "");
-  if (!enable_audio_routing_parameter_value.empty() &&
-      enable_audio_routing_parameter_value != "true" &&
-      enable_audio_routing_parameter_value != "false") {
-    SB_LOG(INFO)
-        << "Invalid value for audio mime parameter \"enableaudiorouting\": "
-        << enable_audio_routing_parameter_value << ".";
+  // Allows for disabling the use of the AudioDeviceCallback API to detect when
+  // audio peripherals are connected. Enabled by default.
+  // (https://developer.android.com/reference/android/media/AudioDeviceCallback)
+  auto enable_audio_device_callback_parameter_value =
+      mime_type.GetParamStringValue("enableaudiodevicecallback", "");
+  if (!enable_audio_device_callback_parameter_value.empty() &&
+      enable_audio_device_callback_parameter_value != "true" &&
+      enable_audio_device_callback_parameter_value != "false") {
+    SB_LOG(INFO) << "Invalid value for audio mime parameter "
+                    "\"enableaudiodevicecallback\": "
+                 << enable_audio_device_callback_parameter_value << ".";
     return false;
   }
   // Allows for enabling tunneled playback. Disabled by default.
@@ -78,6 +78,23 @@
     return false;
   }
 
+  auto audio_passthrough_parameter_value =
+      mime_type.GetParamStringValue("audiopassthrough", "");
+  if (!audio_passthrough_parameter_value.empty() &&
+      audio_passthrough_parameter_value != "true" &&
+      audio_passthrough_parameter_value != "false") {
+    SB_LOG(INFO) << "Invalid value for audio mime parameter "
+                    "\"audiopassthrough\": "
+                 << audio_passthrough_parameter_value
+                 << ". Passthrough is disabled.";
+    return false;
+  }
+  if (audio_passthrough_parameter_value == "false" && is_passthrough) {
+    SB_LOG(INFO) << "Passthrough is rejected because audio mime parameter "
+                    "\"audiopassthrough\" == false.";
+    return false;
+  }
+
   JniEnvExt* env = JniEnvExt::Get();
   ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime));
   const bool must_support_tunnel_mode =
diff --git a/src/starboard/android/shared/player_components_factory.h b/src/starboard/android/shared/player_components_factory.h
index fa64270..b2cbfed 100644
--- a/src/starboard/android/shared/player_components_factory.h
+++ b/src/starboard/android/shared/player_components_factory.h
@@ -64,7 +64,7 @@
 class AudioRendererSinkAndroid : public ::starboard::shared::starboard::player::
                                      filter::AudioRendererSinkImpl {
  public:
-  explicit AudioRendererSinkAndroid(bool enable_audio_routing,
+  explicit AudioRendererSinkAndroid(bool enable_audio_device_callback,
                                     int tunnel_mode_audio_session_id = -1)
       : AudioRendererSinkImpl(
             [=](SbTime start_media_time,
@@ -87,7 +87,8 @@
                   audio_frame_storage_type, frame_buffers,
                   frame_buffers_size_in_frames, update_source_status_func,
                   consume_frames_func, error_func, start_media_time,
-                  tunnel_mode_audio_session_id, enable_audio_routing, context);
+                  tunnel_mode_audio_session_id, enable_audio_device_callback,
+                  context);
             }) {}
 
  private:
@@ -182,9 +183,28 @@
     return (value + alignment - 1) / alignment * alignment;
   }
 
+  static bool IsAudioDeviceCallbackEnabled(
+      const CreationParameters& creation_parameters) {
+    using starboard::shared::starboard::media::MimeType;
+
+    MimeType mime_type(creation_parameters.audio_mime());
+    auto enable_audio_device_callback_parameter_value =
+        mime_type.GetParamStringValue("enableaudiodevicecallback", "");
+    if (enable_audio_device_callback_parameter_value.empty() ||
+        enable_audio_device_callback_parameter_value == "true") {
+      SB_LOG(INFO) << "AudioDeviceCallback is enabled.";
+      return true;
+    }
+    SB_LOG(INFO) << "Mime attribute \"enableaudiodevicecallback\" is set to: "
+                 << enable_audio_device_callback_parameter_value
+                 << ". AudioDeviceCallback is disabled.";
+    return false;
+  }
+
   scoped_ptr<PlayerComponents> CreateComponents(
       const CreationParameters& creation_parameters,
       std::string* error_message) override {
+    using starboard::shared::starboard::media::MimeType;
     SB_DCHECK(error_message);
 
     if (creation_parameters.audio_codec() != kSbMediaAudioCodecAc3 &&
@@ -194,12 +214,21 @@
                                                          error_message);
     }
 
+    MimeType audio_mime_type(creation_parameters.audio_mime());
+    if (audio_mime_type.GetParamStringValue("audiopassthrough", "") ==
+        "false") {
+      SB_LOG(INFO) << "Mime attribute \"audiopassthrough\" is set to: "
+                      "false. Passthrough is disabled.";
+      return scoped_ptr<PlayerComponents>();
+    }
+
     SB_LOG(INFO) << "Creating passthrough components.";
     // TODO: Enable tunnel mode for passthrough
     scoped_ptr<AudioRendererPassthrough> audio_renderer;
     audio_renderer.reset(new AudioRendererPassthrough(
         creation_parameters.audio_sample_info(),
-        GetExtendedDrmSystem(creation_parameters.drm_system())));
+        GetExtendedDrmSystem(creation_parameters.drm_system()),
+        IsAudioDeviceCallbackEnabled(creation_parameters)));
     if (!audio_renderer->is_valid()) {
       return scoped_ptr<PlayerComponents>();
     }
@@ -328,30 +357,20 @@
           creation_parameters.audio_sample_info(),
           GetExtendedDrmSystem(creation_parameters.drm_system()),
           decoder_creator));
-      bool enable_audio_routing = true;
-      MimeType audio_mime_type(creation_parameters.audio_mime());
-      auto enable_audio_routing_parameter_value =
-          audio_mime_type.GetParamStringValue("enableaudiorouting", "");
-      if (enable_audio_routing_parameter_value.empty() ||
-          enable_audio_routing_parameter_value == "true") {
-        SB_LOG(INFO) << "AudioRouting is enabled.";
-      } else {
-        enable_audio_routing = false;
-        SB_LOG(INFO) << "Mime attribute \"enableaudiorouting\" is set to: "
-                     << enable_audio_routing_parameter_value
-                     << ". AudioRouting is disabled.";
-      }
+
+      bool enable_audio_device_callback =
+          IsAudioDeviceCallbackEnabled(creation_parameters);
       if (tunnel_mode_audio_session_id != -1) {
         *audio_renderer_sink = TryToCreateTunnelModeAudioRendererSink(
             tunnel_mode_audio_session_id, creation_parameters,
-            enable_audio_routing);
+            enable_audio_device_callback);
         if (!*audio_renderer_sink) {
           tunnel_mode_audio_session_id = -1;
         }
       }
       if (!*audio_renderer_sink) {
         audio_renderer_sink->reset(
-            new AudioRendererSinkAndroid(enable_audio_routing));
+            new AudioRendererSinkAndroid(enable_audio_device_callback));
       }
     }
 
@@ -523,9 +542,9 @@
   scoped_ptr<AudioRendererSink> TryToCreateTunnelModeAudioRendererSink(
       int tunnel_mode_audio_session_id,
       const CreationParameters& creation_parameters,
-      bool enable_audio_routing) {
+      bool enable_audio_device_callback) {
     scoped_ptr<AudioRendererSink> audio_sink(new AudioRendererSinkAndroid(
-        enable_audio_routing, tunnel_mode_audio_session_id));
+        enable_audio_device_callback, tunnel_mode_audio_session_id));
     // We need to double check if the audio sink can actually be created.
     int max_cached_frames, min_frames_per_append;
     GetAudioRendererParams(creation_parameters, &max_cached_frames,
diff --git a/src/starboard/build/platforms.gni b/src/starboard/build/platforms.gni
index 6a7adcb..b3793c2 100644
--- a/src/starboard/build/platforms.gni
+++ b/src/starboard/build/platforms.gni
@@ -53,4 +53,8 @@
     name = "android-x86"
     path = "starboard/android/x86"
   },
+  {
+    name = "raspi-2"
+    path = "starboard/raspi/2"
+  },
 ]
diff --git a/src/starboard/linux/shared/BUILD.gn b/src/starboard/linux/shared/BUILD.gn
index 522b781..9ef03b9 100644
--- a/src/starboard/linux/shared/BUILD.gn
+++ b/src/starboard/linux/shared/BUILD.gn
@@ -398,6 +398,17 @@
   ]
   deps = [ "//third_party/boringssl:crypto" ]
 
+  if (sb_api_version == 12) {
+    sources += [
+      "//starboard/shared/stub/speech_recognizer_cancel.cc",
+      "//starboard/shared/stub/speech_recognizer_create.cc",
+      "//starboard/shared/stub/speech_recognizer_destroy.cc",
+      "//starboard/shared/stub/speech_recognizer_is_supported.cc",
+      "//starboard/shared/stub/speech_recognizer_start.cc",
+      "//starboard/shared/stub/speech_recognizer_stop.cc",
+    ]
+  }
+
   if (is_internal_build) {
     sources += [
       "//starboard/linux/shared/drm_create_system.cc",
diff --git a/src/starboard/linux/shared/gyp_configuration.py b/src/starboard/linux/shared/gyp_configuration.py
index b8f0949..4e4a96c 100644
--- a/src/starboard/linux/shared/gyp_configuration.py
+++ b/src/starboard/linux/shared/gyp_configuration.py
@@ -121,3 +121,10 @@
           'VideoDecoderTests/VideoDecoderTest.*Invalid*',
       ],
   }
+  # Conditionally disables tests that require ipv6
+  if os.getenv('IPV6_AVAILABLE', 1) == '0':
+    __FILTERED_TESTS['nplb'] = [
+        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDayDestination/1',
+        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceForDestination/1',
+        'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest.SunnyDaySourceNotLoopback/1',
+    ]
diff --git a/src/starboard/nplb/media_can_play_mime_and_key_system_test.cc b/src/starboard/nplb/media_can_play_mime_and_key_system_test.cc
index a117f3c..4c7457c 100644
--- a/src/starboard/nplb/media_can_play_mime_and_key_system_test.cc
+++ b/src/starboard/nplb/media_can_play_mime_and_key_system_test.cc
@@ -175,55 +175,57 @@
 TEST(SbMediaCanPlayMimeAndKeySystem, MinimumSupport) {
   // H.264 High Profile Level 4.2
   SbMediaSupportType result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"avc1.64402a\"; width=1920; height=1080; "
-      "framerate=30; bitrate=20000",
+      "video/mp4; codecs=\"avc1.64002a\"; width=1920; height=1080; "
+      "framerate=30; bitrate=20000000",
       "");
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
   // H.264 Main Profile Level 4.2
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"avc1.4d402a\"; width=1920; height=1080; "
+      "video/mp4; codecs=\"avc1.4d002a\"; width=1920; height=1080; "
       "framerate=30;",
       "");
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"avc1.4d402a\"; width=0; height=0; "
+      "video/mp4; codecs=\"avc1.4d002a\"; width=0; height=0; "
       "framerate=0; bitrate=0",
       "");
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"avc1.4d402a\"; width=-0; height=-0; "
+      "video/mp4; codecs=\"avc1.4d002a\"; width=-0; height=-0; "
       "framerate=-0; bitrate=-0",
       "");
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
   // H.264 Main Profile Level 2.1
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"avc1.4d4015\"; width=432; height=240; "
+      "video/mp4; codecs=\"avc1.4d0015\"; width=432; height=240; "
       "framerate=15;",
       "");
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
   // AV1 Main Profile 1080p
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"av01.0.05M.08\"; width=1920; height=1080; "
-      "framerate=30; bitrate=20000",
+      "video/mp4; codecs=\"av01.0.09M.08\"; width=1920; height=1080; "
+      "framerate=30; bitrate=20000000",
       "");
 
   // VP9 1080p
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/webm; codecs=\"vp9\"; width=1920; height=1080; framerate=60", "");
+      "video/webm; codecs=\"vp9\"; width=1920; height=1080; framerate=60; "
+      "bitrate=20000000",
+      "");
 
   // AAC-LC
   result = SbMediaCanPlayMimeAndKeySystem(
-      "audio/mp4; codecs=\"mp4a.40.2\"; channels=2; bitrate=256;", "");
+      "audio/mp4; codecs=\"mp4a.40.2\"; channels=2; bitrate=256000;", "");
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
   // HE-AAC
   result = SbMediaCanPlayMimeAndKeySystem(
-      "audio/mp4; codecs=\"mp4a.40.5\"; channels=2; bitrate=48;", "");
+      "audio/mp4; codecs=\"mp4a.40.5\"; channels=2; bitrate=48000;", "");
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 }
 
@@ -286,6 +288,7 @@
   }
 }
 
+// TODO: Create an abstraction to shorten the length of this test.
 TEST(SbMediaCanPlayMimeAndKeySystem, PrintMaximumSupport) {
   // AVC
   std::string avc_resolution = "Unsupported";
@@ -298,14 +301,14 @@
     avc_resolution = "1080p";
     // 2K
     result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"avc1.64402a\"; width=2560; height=1440; "
+        "video/mp4; codecs=\"avc1.64002a\"; width=2560; height=1440; "
         "framerate=30",
         "");
     if (result == kSbMediaSupportTypeProbably) {
       avc_resolution = "2K";
       // 4K
       result = SbMediaCanPlayMimeAndKeySystem(
-          "video/mp4; codecs=\"avc1.64402a\"; width=3840; height=2160; "
+          "video/mp4; codecs=\"avc1.64002a\"; width=3840; height=2160; "
           "framerate=30",
           "");
       if (result == kSbMediaSupportTypeProbably) {
@@ -392,7 +395,7 @@
   // 1080p
   result = SbMediaCanPlayMimeAndKeySystem(
       "video/webm; codecs=\"vp09.02.51.10.01.09.16.09.00\"; width=1920; "
-      "height=1080",
+      "height=1080; framerate=30",
       "");
   if (result == kSbMediaSupportTypeProbably) {
     vp9_hdr_resolution = "1080p";
@@ -418,21 +421,21 @@
   std::string av1_resolution = "Unsupported";
   // 1080p
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"av01.0.08M.08\"; width=1920; "
+      "video/mp4; codecs=\"av01.0.09M.08\"; width=1920; "
       "height=1080; framerate=30",
       "");
   if (result == kSbMediaSupportTypeProbably) {
     av1_resolution = "1080p";
     // 2K
     result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"av01.0.08M.08\"; width=2560; "
+        "video/mp4; codecs=\"av01.0.12M.08\"; width=2560; "
         "height=1440; framerate=30",
         "");
     if (result == kSbMediaSupportTypeProbably) {
       av1_resolution = "2K";
       // 4K
       result = SbMediaCanPlayMimeAndKeySystem(
-          "video/mp4; codecs=\"av01.0.08M.08\"; width=3840; "
+          "video/mp4; codecs=\"av01.0.12M.08\"; width=3840; "
           "height=2160; framerate=30",
           "");
       if (result == kSbMediaSupportTypeProbably) {
@@ -444,22 +447,22 @@
   std::string av1_hfr_resolution = "Unsupported";
   // 1080p
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"av01.0.08M.08\"; width=1920; "
-      "height=1080; framerate=60",
+      "video/mp4; codecs=\"av01.0.09M.08\"; width=1920; height=1080; "
+      "framerate=60",
       "");
   if (result == kSbMediaSupportTypeProbably) {
     av1_hfr_resolution = "1080p";
     // 2K
     result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"av01.0.08M.08\"; width=2560; "
-        "height=1440; framerate=60",
+        "video/mp4; codecs=\"av01.0.12M.08\"; width=2560; height=1440; "
+        "framerate=60",
         "");
     if (result == kSbMediaSupportTypeProbably) {
       av1_hfr_resolution = "2K";
       // 4K
       result = SbMediaCanPlayMimeAndKeySystem(
-          "video/mp4; codecs=\"av01.0.08M.08\"; width=3840; "
-          "height=2160; framerate=60",
+          "video/mp4; codecs=\"av01.0.13M.08\"; width=3840; height=2160; "
+          "framerate=60",
           "");
       if (result == kSbMediaSupportTypeProbably) {
         av1_hfr_resolution = "4K";
@@ -472,21 +475,21 @@
   // 1080p
   result = SbMediaCanPlayMimeAndKeySystem(
       "video/mp4; codecs=\"av01.0.09M.10.0.110.09.16.09.0\"; width=1920; "
-      "height=1080; framerate=30",
+      "height=1080",
       "");
   if (result == kSbMediaSupportTypeProbably) {
     av1_hdr_resolution = "1080p";
     // 2K
     result = SbMediaCanPlayMimeAndKeySystem(
         "video/mp4; codecs=\"av01.0.12M.10.0.110.09.16.09.0\"; width=2560; "
-        "height=1440; framerate=30",
+        "height=1440",
         "");
     if (result == kSbMediaSupportTypeProbably) {
       av1_hdr_resolution = "2K";
       // 4K
       result = SbMediaCanPlayMimeAndKeySystem(
           "video/mp4; codecs=\"av01.0.13M.10.0.110.09.16.09.0\"; width=3840; "
-          "height=2160; framerate=30",
+          "height=2160",
           "");
       if (result == kSbMediaSupportTypeProbably) {
         av1_hdr_resolution = "4K";
@@ -514,7 +517,7 @@
   std::string opus_support = "Unsupported";
   result = SbMediaCanPlayMimeAndKeySystem(
       "audio/webm; codecs=\"opus\"; "
-      "channels=2; bitrate=576;",
+      "channels=2; bitrate=128000;",
       "");
   if (result == kSbMediaSupportTypeProbably) {
     opus_support = "Supported";
@@ -524,7 +527,7 @@
   std::string opus51_support = "Unsupported";
   result = SbMediaCanPlayMimeAndKeySystem(
       "audio/webm; codecs=\"opus\"; "
-      "channels=6; bitrate=576;",
+      "channels=6; bitrate=576000;",
       "");
   if (result == kSbMediaSupportTypeProbably) {
     opus51_support = "Supported";
@@ -533,7 +536,7 @@
   // AC-3
   std::string ac3_support = "Unsupported";
   result = SbMediaCanPlayMimeAndKeySystem(
-      "audio/mp4; codecs=\"ac-3\"; channels=2", "");
+      "audio/mp4; codecs=\"ac-3\"; channels=6; bitrate=512000", "");
   if (result == kSbMediaSupportTypeProbably) {
     ac3_support = "Supported";
   }
@@ -541,7 +544,7 @@
   // E-AC-3
   std::string eac3_support = "Unsupported";
   result = SbMediaCanPlayMimeAndKeySystem(
-      "audio/mp4; codecs=\"ec-3\"; channels=2", "");
+      "audio/mp4; codecs=\"ec-3\"; channels=6; bitrate=512000", "");
   if (result == kSbMediaSupportTypeProbably) {
     eac3_support = "Supported";
   }
@@ -561,59 +564,60 @@
                << "\n\tAC-3: " << ac3_support << "\n\tE-AC-3: " << eac3_support;
 }
 
+// TODO: Create an abstraction to shorten the length of this test.
 TEST(SbMediaCanPlayMimeAndKeySystem, ValidateQueriesUnderPeakCapability) {
   // H.264 High Profile Level 4.2 1080p 25 fps
   SbMediaSupportType result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"avc1.64402a\"; width=1920; height=1080; "
+      "video/mp4; codecs=\"avc1.64002a\"; width=1920; height=1080; "
       "framerate=25",
       "");
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
   // H.264 High Profile Level 4.2 1080p 24 fps
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"avc1.64402a\"; width=1920; height=1080; "
+      "video/mp4; codecs=\"avc1.64002a\"; width=1920; height=1080; "
       "framerate=24",
       "");
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
   // H.264 High Profile Level 4.2 1920x818
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"avc1.64402a\"; width=1920; height=818; "
+      "video/mp4; codecs=\"avc1.64002a\"; width=1920; height=818; "
       "framerate=30",
       "");
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
   // H.264 High Profile Level 4.2 720p
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"avc1.64402a\"; width=1280; height=720; "
+      "video/mp4; codecs=\"avc1.64002a\"; width=1280; height=720; "
       "framerate=30",
       "");
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
   // H.264 High Profile Level 4.2 480p
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"avc1.64402a\"; width=640; height=480; "
+      "video/mp4; codecs=\"avc1.64002a\"; width=640; height=480; "
       "framerate=30",
       "");
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
   // H.264 High Profile Level 4.2 360p
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"avc1.64402a\"; width=480; height=360; "
+      "video/mp4; codecs=\"avc1.64002a\"; width=480; height=360; "
       "framerate=30",
       "");
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
   // H.264 High Profile Level 4.2 240p
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"avc1.64402a\"; width=352; height=240; "
+      "video/mp4; codecs=\"avc1.64002a\"; width=352; height=240; "
       "framerate=30",
       "");
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
   // H.264 High Profile Level 4.2 144p
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"avc1.64402a\"; width=256; height=144; "
+      "video/mp4; codecs=\"avc1.64002a\"; width=256; height=144; "
       "framerate=30",
       "");
   ASSERT_EQ(result, kSbMediaSupportTypeProbably);
@@ -635,78 +639,78 @@
 
   // AV1 Main Profile 4K
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"av01.0.13M.10.0.110.09.16.09.0\"; width=3840; "
-      "height=2160; framerate=30",
+      "video/mp4; codecs=\"av01.0.12M.08\"; width=3840; height=2160; "
+      "framerate=30",
       "");
 
   if (result == kSbMediaSupportTypeProbably) {
     // AV1 Main Profile 1440p
     result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"av01.0.12M.10.0.110.09.16.09.0\"; width=2560; "
-        "height=1440; framerate=30",
+        "video/mp4; codecs=\"av01.0.12M.08\"; width=2560; height=1440; "
+        "framerate=30",
         "");
     ASSERT_EQ(result, kSbMediaSupportTypeProbably);
   }
   // AV1 Main Profile 1080p
   result = SbMediaCanPlayMimeAndKeySystem(
-      "video/mp4; codecs=\"av01.0.09M.08.0.110.09.16.09.0\"; width=1920; "
-      "height=1080; framerate=30",
+      "video/mp4; codecs=\"av01.0.09M.08\"; width=1920; height=1080; "
+      "framerate=30",
       "");
 
   if (result == kSbMediaSupportTypeProbably) {
     // AV1 Main Profile 1080p 25 fps
     result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"av01.0.09M.08.0.110.09.16.09.0\"; width=1920; "
-        "height=1080; framerate=25",
+        "video/mp4; codecs=\"av01.0.09M.08\"; width=1920; height=1080; "
+        "framerate=25",
         "");
     ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
     // AV1 Main Profile 1080p 24 fps
     result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"av01.0.09M.08.0.110.09.16.09.0\"; width=1920; "
-        "height=1080; framerate=24",
+        "video/mp4; codecs=\"av01.0.09M.08\"; width=1920; height=1080; "
+        "framerate=24",
         "");
     ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
     // AV1 Main Profile 1920x818
     result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"av01.0.09M.08.0.110.09.16.09.0\"; width=1920; "
-        "height=818; framerate=30",
+        "video/mp4; codecs=\"av01.0.09M.08\"; width=1920; height=818; "
+        "framerate=30",
         "");
     ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
     // AV1 Main Profile 720p
     result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"av01.0.08M.10.0.110.09.16.09.0\"; width=1280; "
-        "height=720; framerate=30",
+        "video/mp4; codecs=\"av01.0.05M.08\"; width=1280; height=720; "
+        "framerate=30",
         "");
     ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
     // AV1 Main Profile 480p
     result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"av01.0.04M.10.0.110.09.16.09.0\"; width=854; "
-        "height=480; framerate=30",
+        "video/mp4; codecs=\"av01.0.04M.08\"; width=854; height=480; "
+        "framerate=30",
         "");
     ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
     // AV1 Main Profile 360p
     result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"av01.0.01M.10.0.110.09.16.09.0\"; width=640; "
-        "height=360; framerate=30",
+        "video/mp4; codecs=\"av01.0.01M.08\"; width=640; height=360; "
+        "framerate=30",
         "");
     ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
     // AV1 Main Profile 240p
     result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"av01.0.00M.10.0.110.09.16.09.0\"; width=426; "
-        "height=240; framerate=30",
+        "video/mp4; codecs=\"av01.0.00M.08\"; width=426; height=240; "
+        "framerate=30",
         "");
     ASSERT_EQ(result, kSbMediaSupportTypeProbably);
 
     // AV1 Main Profile 144p
     result = SbMediaCanPlayMimeAndKeySystem(
-        "video/mp4; codecs=\"av01.0.00M.10.0.110.09.16.09.0\"; width=256; "
-        "height=144; framerate=30",
+        "video/mp4; codecs=\"av01.0.00M.08\"; width=256; height=144; "
+        "framerate=30",
         "");
     ASSERT_EQ(result, kSbMediaSupportTypeProbably);
   }
diff --git a/src/starboard/raspi/2/BUILD.gn b/src/starboard/raspi/2/BUILD.gn
new file mode 100644
index 0000000..3cf94e9
--- /dev/null
+++ b/src/starboard/raspi/2/BUILD.gn
@@ -0,0 +1,20 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+static_library("starboard_platform") {
+  check_includes = false
+  configs += [ "//starboard/build/config:starboard_implementation" ]
+
+  public_deps = [ "//starboard/raspi/shared:starboard_platform" ]
+}
diff --git a/src/starboard/raspi/2/platform_configuration/BUILD.gn b/src/starboard/raspi/2/platform_configuration/BUILD.gn
new file mode 100644
index 0000000..ef03b60
--- /dev/null
+++ b/src/starboard/raspi/2/platform_configuration/BUILD.gn
@@ -0,0 +1,27 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+config("platform_configuration") {
+  configs = [
+    "//starboard/build/config/sabi",
+    "//starboard/raspi/shared/platform_configuration",
+  ]
+  cflags = [
+    "-march=armv7-a",
+    "-mfpu=neon-vfpv4",
+    "-mfloat-abi=hard",
+    "-mcpu=cortex-a8",
+    "-mtune=cortex-a8",
+  ]
+}
diff --git a/src/starboard/raspi/2/platform_configuration/configuration.gni b/src/starboard/raspi/2/platform_configuration/configuration.gni
new file mode 100644
index 0000000..77ec5a9
--- /dev/null
+++ b/src/starboard/raspi/2/platform_configuration/configuration.gni
@@ -0,0 +1,20 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//starboard/raspi/shared/platform_configuration/configuration.gni")
+
+arm_float_abi = "hard"
+
+sb_evergreen_compatible_use_libunwind = true
+sb_is_evergreen_compatible = true
diff --git a/src/starboard/raspi/2/toolchain/BUILD.gn b/src/starboard/raspi/2/toolchain/BUILD.gn
new file mode 100644
index 0000000..57a35ca
--- /dev/null
+++ b/src/starboard/raspi/2/toolchain/BUILD.gn
@@ -0,0 +1,37 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//build/toolchain/gcc_toolchain.gni")
+
+_home_dir = getenv("HOME")
+_clang_base_path = "$_home_dir/starboard-toolchains/x86_64-linux-gnu-clang-chromium-365097-f7e52fbd-8"
+raspi_toolchain_path = "$_home_dir/raspi_tools/tools/arm-bcm2708/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin"
+
+clang_toolchain("host") {
+  clang_base_path = _clang_base_path
+}
+
+gcc_toolchain("target") {
+  cc = "$raspi_toolchain_path/arm-linux-gnueabihf-gcc"
+  cxx = "$raspi_toolchain_path/arm-linux-gnueabihf-g++"
+  ld = cxx
+
+  # We use whatever 'ar' resolves to in gyp.
+  ar = "ar"
+  strip = "$raspi_toolchain_path/arm-linux-gnueabihf-strip"
+
+  toolchain_args = {
+    is_clang = false
+  }
+}
diff --git a/src/starboard/raspi/shared/BUILD.gn b/src/starboard/raspi/shared/BUILD.gn
new file mode 100644
index 0000000..37c97e7
--- /dev/null
+++ b/src/starboard/raspi/shared/BUILD.gn
@@ -0,0 +1,412 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import("//starboard/shared/starboard/player/buildfiles.gni")
+
+group("starboard_platform") {
+  public_deps = [ ":starboard_platform_sources" ]
+}
+
+static_library("starboard_platform_sources") {
+  sources = [
+    "//starboard/linux/shared/atomic_public.h",
+    "//starboard/linux/shared/configuration_constants.cc",
+    "//starboard/linux/shared/configuration_public.h",
+    "//starboard/linux/shared/netlink.cc",
+    "//starboard/linux/shared/netlink.h",
+    "//starboard/linux/shared/routes.cc",
+    "//starboard/linux/shared/routes.h",
+    "//starboard/linux/shared/system_get_connection_type.cc",
+    "//starboard/linux/shared/system_get_path.cc",
+    "//starboard/linux/shared/system_has_capability.cc",
+    "//starboard/raspi/shared/application_dispmanx.cc",
+    "//starboard/raspi/shared/audio_sink_type_dispatcher.cc",
+    "//starboard/raspi/shared/configuration.cc",
+    "//starboard/raspi/shared/configuration.h",
+    "//starboard/raspi/shared/dispmanx_util.cc",
+    "//starboard/raspi/shared/dispmanx_util.h",
+    "//starboard/raspi/shared/graphics.cc",
+    "//starboard/raspi/shared/graphics.h",
+    "//starboard/raspi/shared/main.cc",
+    "//starboard/raspi/shared/media_is_video_supported.cc",
+    "//starboard/raspi/shared/open_max/decode_target_create.cc",
+    "//starboard/raspi/shared/open_max/decode_target_create.h",
+    "//starboard/raspi/shared/open_max/decode_target_get_info.cc",
+    "//starboard/raspi/shared/open_max/decode_target_internal.h",
+    "//starboard/raspi/shared/open_max/decode_target_release.cc",
+    "//starboard/raspi/shared/open_max/dispmanx_resource_pool.cc",
+    "//starboard/raspi/shared/open_max/dispmanx_resource_pool.h",
+    "//starboard/raspi/shared/open_max/image_decode.cc",
+    "//starboard/raspi/shared/open_max/image_is_decode_supported.cc",
+    "//starboard/raspi/shared/open_max/open_max_component.cc",
+    "//starboard/raspi/shared/open_max/open_max_component.h",
+    "//starboard/raspi/shared/open_max/open_max_component_base.cc",
+    "//starboard/raspi/shared/open_max/open_max_component_base.h",
+    "//starboard/raspi/shared/open_max/open_max_egl_render_component.cc",
+    "//starboard/raspi/shared/open_max/open_max_egl_render_component.h",
+    "//starboard/raspi/shared/open_max/open_max_image_decode_component.cc",
+    "//starboard/raspi/shared/open_max/open_max_image_decode_component.h",
+    "//starboard/raspi/shared/open_max/open_max_video_decode_component.cc",
+    "//starboard/raspi/shared/open_max/open_max_video_decode_component.h",
+    "//starboard/raspi/shared/open_max/video_decoder.cc",
+    "//starboard/raspi/shared/open_max/video_decoder.h",
+    "//starboard/raspi/shared/player_components_factory.cc",
+    "//starboard/raspi/shared/system_get_device_type.cc",
+    "//starboard/raspi/shared/system_get_extensions.cc",
+    "//starboard/raspi/shared/system_get_property.cc",
+    "//starboard/raspi/shared/system_gles2.cc",
+    "//starboard/raspi/shared/thread_create_priority.cc",
+    "//starboard/raspi/shared/video_renderer_sink_impl.cc",
+    "//starboard/raspi/shared/video_renderer_sink_impl.h",
+    "//starboard/raspi/shared/window_create.cc",
+    "//starboard/raspi/shared/window_destroy.cc",
+    "//starboard/raspi/shared/window_get_platform_handle.cc",
+    "//starboard/raspi/shared/window_get_size.cc",
+    "//starboard/raspi/shared/window_internal.cc",
+    "//starboard/shared/alsa/alsa_audio_sink_type.cc",
+    "//starboard/shared/alsa/alsa_audio_sink_type.h",
+    "//starboard/shared/alsa/alsa_util.cc",
+    "//starboard/shared/alsa/alsa_util.h",
+    "//starboard/shared/dlmalloc/memory_map.cc",
+    "//starboard/shared/dlmalloc/memory_protect.cc",
+    "//starboard/shared/dlmalloc/memory_unmap.cc",
+    "//starboard/shared/egl/system_egl.cc",
+    "//starboard/shared/gcc/atomic_gcc_public.h",
+    "//starboard/shared/iso/character_is_alphanumeric.cc",
+    "//starboard/shared/iso/character_is_digit.cc",
+    "//starboard/shared/iso/character_is_hex_digit.cc",
+    "//starboard/shared/iso/character_is_space.cc",
+    "//starboard/shared/iso/character_is_upper.cc",
+    "//starboard/shared/iso/character_to_lower.cc",
+    "//starboard/shared/iso/character_to_upper.cc",
+    "//starboard/shared/iso/directory_close.cc",
+    "//starboard/shared/iso/directory_get_next.cc",
+    "//starboard/shared/iso/directory_open.cc",
+    "//starboard/shared/iso/double_absolute.cc",
+    "//starboard/shared/iso/double_exponent.cc",
+    "//starboard/shared/iso/double_floor.cc",
+    "//starboard/shared/iso/double_is_finite.cc",
+    "//starboard/shared/iso/double_is_nan.cc",
+    "//starboard/shared/iso/memory_allocate_unchecked.cc",
+    "//starboard/shared/iso/memory_compare.cc",
+    "//starboard/shared/iso/memory_copy.cc",
+    "//starboard/shared/iso/memory_find_byte.cc",
+    "//starboard/shared/iso/memory_free.cc",
+    "//starboard/shared/iso/memory_move.cc",
+    "//starboard/shared/iso/memory_reallocate_unchecked.cc",
+    "//starboard/shared/iso/memory_set.cc",
+    "//starboard/shared/iso/string_compare.cc",
+    "//starboard/shared/iso/string_compare_all.cc",
+    "//starboard/shared/iso/string_find_character.cc",
+    "//starboard/shared/iso/string_find_last_character.cc",
+    "//starboard/shared/iso/string_find_string.cc",
+    "//starboard/shared/iso/string_get_length.cc",
+    "//starboard/shared/iso/string_get_length_wide.cc",
+    "//starboard/shared/iso/string_parse_double.cc",
+    "//starboard/shared/iso/string_parse_signed_integer.cc",
+    "//starboard/shared/iso/string_parse_uint64.cc",
+    "//starboard/shared/iso/string_parse_unsigned_integer.cc",
+    "//starboard/shared/iso/string_scan.cc",
+    "//starboard/shared/iso/system_binary_search.cc",
+    "//starboard/shared/iso/system_sort.cc",
+    "//starboard/shared/libevent/socket_waiter_add.cc",
+    "//starboard/shared/libevent/socket_waiter_create.cc",
+    "//starboard/shared/libevent/socket_waiter_destroy.cc",
+    "//starboard/shared/libevent/socket_waiter_internal.cc",
+    "//starboard/shared/libevent/socket_waiter_remove.cc",
+    "//starboard/shared/libevent/socket_waiter_wait.cc",
+    "//starboard/shared/libevent/socket_waiter_wait_timed.cc",
+    "//starboard/shared/libevent/socket_waiter_wake_up.cc",
+    "//starboard/shared/linux/byte_swap.cc",
+    "//starboard/shared/linux/cpu_features_get.cc",
+    "//starboard/shared/linux/dev_input/dev_input.cc",
+    "//starboard/shared/linux/get_home_directory.cc",
+    "//starboard/shared/linux/memory_get_stack_bounds.cc",
+    "//starboard/shared/linux/page_internal.cc",
+    "//starboard/shared/linux/socket_get_interface_address.cc",
+    "//starboard/shared/linux/system_get_random_data.cc",
+    "//starboard/shared/linux/system_get_stack.cc",
+    "//starboard/shared/linux/system_get_total_cpu_memory.cc",
+    "//starboard/shared/linux/system_get_used_cpu_memory.cc",
+    "//starboard/shared/linux/system_is_debugger_attached.cc",
+    "//starboard/shared/linux/system_symbolize.cc",
+    "//starboard/shared/linux/thread_get_id.cc",
+    "//starboard/shared/linux/thread_get_name.cc",
+    "//starboard/shared/linux/thread_set_name.cc",
+    "//starboard/shared/nouser/user_get_current.cc",
+    "//starboard/shared/nouser/user_get_property.cc",
+    "//starboard/shared/nouser/user_get_signed_in.cc",
+    "//starboard/shared/nouser/user_internal.cc",
+    "//starboard/shared/opus/opus_audio_decoder.cc",
+    "//starboard/shared/opus/opus_audio_decoder.h",
+    "//starboard/shared/posix/directory_create.cc",
+    "//starboard/shared/posix/file_atomic_replace.cc",
+    "//starboard/shared/posix/file_can_open.cc",
+    "//starboard/shared/posix/file_close.cc",
+    "//starboard/shared/posix/file_delete.cc",
+    "//starboard/shared/posix/file_exists.cc",
+    "//starboard/shared/posix/file_flush.cc",
+    "//starboard/shared/posix/file_get_info.cc",
+    "//starboard/shared/posix/file_get_path_info.cc",
+    "//starboard/shared/posix/file_open.cc",
+    "//starboard/shared/posix/file_read.cc",
+    "//starboard/shared/posix/file_seek.cc",
+    "//starboard/shared/posix/file_truncate.cc",
+    "//starboard/shared/posix/file_write.cc",
+    "//starboard/shared/posix/log.cc",
+    "//starboard/shared/posix/log_flush.cc",
+    "//starboard/shared/posix/log_format.cc",
+    "//starboard/shared/posix/log_is_tty.cc",
+    "//starboard/shared/posix/log_raw.cc",
+    "//starboard/shared/posix/memory_allocate_aligned_unchecked.cc",
+    "//starboard/shared/posix/memory_flush.cc",
+    "//starboard/shared/posix/memory_free_aligned.cc",
+    "//starboard/shared/posix/set_non_blocking_internal.cc",
+    "//starboard/shared/posix/socket_accept.cc",
+    "//starboard/shared/posix/socket_bind.cc",
+    "//starboard/shared/posix/socket_clear_last_error.cc",
+    "//starboard/shared/posix/socket_connect.cc",
+    "//starboard/shared/posix/socket_create.cc",
+    "//starboard/shared/posix/socket_destroy.cc",
+    "//starboard/shared/posix/socket_free_resolution.cc",
+    "//starboard/shared/posix/socket_get_last_error.cc",
+    "//starboard/shared/posix/socket_get_local_address.cc",
+    "//starboard/shared/posix/socket_internal.cc",
+    "//starboard/shared/posix/socket_is_connected.cc",
+    "//starboard/shared/posix/socket_is_connected_and_idle.cc",
+    "//starboard/shared/posix/socket_is_ipv6_supported.cc",
+    "//starboard/shared/posix/socket_join_multicast_group.cc",
+    "//starboard/shared/posix/socket_listen.cc",
+    "//starboard/shared/posix/socket_receive_from.cc",
+    "//starboard/shared/posix/socket_resolve.cc",
+    "//starboard/shared/posix/socket_send_to.cc",
+    "//starboard/shared/posix/socket_set_broadcast.cc",
+    "//starboard/shared/posix/socket_set_receive_buffer_size.cc",
+    "//starboard/shared/posix/socket_set_reuse_address.cc",
+    "//starboard/shared/posix/socket_set_send_buffer_size.cc",
+    "//starboard/shared/posix/socket_set_tcp_keep_alive.cc",
+    "//starboard/shared/posix/socket_set_tcp_no_delay.cc",
+    "//starboard/shared/posix/socket_set_tcp_window_scaling.cc",
+    "//starboard/shared/posix/storage_write_record.cc",
+    "//starboard/shared/posix/string_compare_no_case.cc",
+    "//starboard/shared/posix/string_compare_no_case_n.cc",
+    "//starboard/shared/posix/string_compare_wide.cc",
+    "//starboard/shared/posix/string_format.cc",
+    "//starboard/shared/posix/string_format_wide.cc",
+    "//starboard/shared/posix/system_break_into_debugger.cc",
+    "//starboard/shared/posix/system_clear_last_error.cc",
+    "//starboard/shared/posix/system_get_error_string.cc",
+    "//starboard/shared/posix/system_get_last_error.cc",
+    "//starboard/shared/posix/system_get_locale_id.cc",
+    "//starboard/shared/posix/system_get_number_of_processors.cc",
+    "//starboard/shared/posix/thread_sleep.cc",
+    "//starboard/shared/posix/time_get_monotonic_now.cc",
+    "//starboard/shared/posix/time_get_monotonic_thread_now.cc",
+    "//starboard/shared/posix/time_get_now.cc",
+    "//starboard/shared/posix/time_is_time_thread_now_supported.cc",
+    "//starboard/shared/posix/time_zone_get_current.cc",
+    "//starboard/shared/posix/time_zone_get_name.cc",
+    "//starboard/shared/pthread/condition_variable_broadcast.cc",
+    "//starboard/shared/pthread/condition_variable_create.cc",
+    "//starboard/shared/pthread/condition_variable_destroy.cc",
+    "//starboard/shared/pthread/condition_variable_signal.cc",
+    "//starboard/shared/pthread/condition_variable_wait.cc",
+    "//starboard/shared/pthread/condition_variable_wait_timed.cc",
+    "//starboard/shared/pthread/mutex_acquire.cc",
+    "//starboard/shared/pthread/mutex_acquire_try.cc",
+    "//starboard/shared/pthread/mutex_create.cc",
+    "//starboard/shared/pthread/mutex_destroy.cc",
+    "//starboard/shared/pthread/mutex_release.cc",
+    "//starboard/shared/pthread/once.cc",
+    "//starboard/shared/pthread/thread_context_get_pointer.cc",
+    "//starboard/shared/pthread/thread_context_internal.cc",
+    "//starboard/shared/pthread/thread_context_internal.h",
+    "//starboard/shared/pthread/thread_create.cc",
+    "//starboard/shared/pthread/thread_create_local_key.cc",
+    "//starboard/shared/pthread/thread_create_priority.h",
+    "//starboard/shared/pthread/thread_destroy_local_key.cc",
+    "//starboard/shared/pthread/thread_detach.cc",
+    "//starboard/shared/pthread/thread_get_current.cc",
+    "//starboard/shared/pthread/thread_get_local_value.cc",
+    "//starboard/shared/pthread/thread_is_equal.cc",
+    "//starboard/shared/pthread/thread_join.cc",
+    "//starboard/shared/pthread/thread_sampler_create.cc",
+    "//starboard/shared/pthread/thread_sampler_destroy.cc",
+    "//starboard/shared/pthread/thread_sampler_freeze.cc",
+    "//starboard/shared/pthread/thread_sampler_internal.cc",
+    "//starboard/shared/pthread/thread_sampler_internal.h",
+    "//starboard/shared/pthread/thread_sampler_is_supported.cc",
+    "//starboard/shared/pthread/thread_sampler_thaw.cc",
+    "//starboard/shared/pthread/thread_set_local_value.cc",
+    "//starboard/shared/pthread/thread_yield.cc",
+    "//starboard/shared/signal/crash_signals.cc",
+    "//starboard/shared/signal/crash_signals.h",
+    "//starboard/shared/signal/suspend_signals.cc",
+    "//starboard/shared/signal/suspend_signals.h",
+    "//starboard/shared/signal/system_request_conceal.cc",
+    "//starboard/shared/signal/system_request_freeze.cc",
+    "//starboard/shared/signal/system_request_suspend.cc",
+    "//starboard/shared/starboard/application.cc",
+    "//starboard/shared/starboard/audio_sink/audio_sink_create.cc",
+    "//starboard/shared/starboard/audio_sink/audio_sink_destroy.cc",
+    "//starboard/shared/starboard/audio_sink/audio_sink_get_max_channels_5_1.cc",
+    "//starboard/shared/starboard/audio_sink/audio_sink_get_min_buffer_size_in_frames.cc",
+    "//starboard/shared/starboard/audio_sink/audio_sink_get_nearest_supported_sample_frequency.cc",
+    "//starboard/shared/starboard/audio_sink/audio_sink_internal.cc",
+    "//starboard/shared/starboard/audio_sink/audio_sink_internal.h",
+    "//starboard/shared/starboard/audio_sink/audio_sink_is_audio_frame_storage_type_supported_interleaved_only.cc",
+    "//starboard/shared/starboard/audio_sink/audio_sink_is_audio_sample_type_supported_float32_only.cc",
+    "//starboard/shared/starboard/audio_sink/audio_sink_is_valid.cc",
+    "//starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc",
+    "//starboard/shared/starboard/audio_sink/stub_audio_sink_type.h",
+    "//starboard/shared/starboard/command_line.cc",
+    "//starboard/shared/starboard/command_line.h",
+    "//starboard/shared/starboard/crash_handler.cc",
+    "//starboard/shared/starboard/crash_handler.h",
+    "//starboard/shared/starboard/directory_can_open.cc",
+    "//starboard/shared/starboard/event_cancel.cc",
+    "//starboard/shared/starboard/event_schedule.cc",
+    "//starboard/shared/starboard/file_atomic_replace_write_file.cc",
+    "//starboard/shared/starboard/file_atomic_replace_write_file.h",
+    "//starboard/shared/starboard/file_mode_string_to_flags.cc",
+    "//starboard/shared/starboard/file_storage/storage_close_record.cc",
+    "//starboard/shared/starboard/file_storage/storage_delete_record.cc",
+    "//starboard/shared/starboard/file_storage/storage_get_record_size.cc",
+    "//starboard/shared/starboard/file_storage/storage_open_record.cc",
+    "//starboard/shared/starboard/file_storage/storage_read_record.cc",
+    "//starboard/shared/starboard/log_mutex.cc",
+    "//starboard/shared/starboard/log_mutex.h",
+    "//starboard/shared/starboard/log_raw_dump_stack.cc",
+    "//starboard/shared/starboard/log_raw_format.cc",
+    "//starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc",
+    "//starboard/shared/starboard/media/media_get_audio_buffer_budget.cc",
+    "//starboard/shared/starboard/media/media_get_audio_configuration_5_1.cc",
+    "//starboard/shared/starboard/media/media_get_audio_output_count_single_audio_output.cc",
+    "//starboard/shared/starboard/media/media_get_buffer_alignment.cc",
+    "//starboard/shared/starboard/media/media_get_buffer_allocation_unit.cc",
+    "//starboard/shared/starboard/media/media_get_buffer_garbage_collection_duration_threshold.cc",
+    "//starboard/shared/starboard/media/media_get_buffer_padding.cc",
+    "//starboard/shared/starboard/media/media_get_buffer_storage_type.cc",
+    "//starboard/shared/starboard/media/media_get_initial_buffer_capacity.cc",
+    "//starboard/shared/starboard/media/media_get_max_buffer_capacity.cc",
+    "//starboard/shared/starboard/media/media_get_progressive_buffer_budget.cc",
+    "//starboard/shared/starboard/media/media_get_video_buffer_budget.cc",
+    "//starboard/shared/starboard/media/media_is_audio_supported_aac_and_opus.cc",
+    "//starboard/shared/starboard/media/media_is_buffer_pool_allocate_on_demand.cc",
+    "//starboard/shared/starboard/media/media_is_buffer_using_memory_pool.cc",
+    "//starboard/shared/starboard/media/media_is_transfer_characteristics_supported.cc",
+    "//starboard/shared/starboard/media/mime_type.cc",
+    "//starboard/shared/starboard/media/mime_type.h",
+    "//starboard/shared/starboard/memory.cc",
+    "//starboard/shared/starboard/new.cc",
+    "//starboard/shared/starboard/queue_application.cc",
+    "//starboard/shared/starboard/string_concat.cc",
+    "//starboard/shared/starboard/string_concat_wide.cc",
+    "//starboard/shared/starboard/string_copy.cc",
+    "//starboard/shared/starboard/string_copy_wide.cc",
+    "//starboard/shared/starboard/string_duplicate.cc",
+    "//starboard/shared/starboard/system_get_random_uint64.cc",
+    "//starboard/shared/starboard/system_request_blur.cc",
+    "//starboard/shared/starboard/system_request_focus.cc",
+    "//starboard/shared/starboard/system_request_pause.cc",
+    "//starboard/shared/starboard/system_request_reveal.cc",
+    "//starboard/shared/starboard/system_request_stop.cc",
+    "//starboard/shared/starboard/system_request_unpause.cc",
+    "//starboard/shared/starboard/system_supports_resume.cc",
+    "//starboard/shared/starboard/window_set_default_options.cc",
+    "//starboard/shared/stub/accessibility_get_caption_settings.cc",
+    "//starboard/shared/stub/accessibility_get_display_settings.cc",
+    "//starboard/shared/stub/accessibility_get_text_to_speech_settings.cc",
+    "//starboard/shared/stub/accessibility_set_captions_enabled.cc",
+    "//starboard/shared/stub/cryptography_create_transformer.cc",
+    "//starboard/shared/stub/cryptography_destroy_transformer.cc",
+    "//starboard/shared/stub/cryptography_get_tag.cc",
+    "//starboard/shared/stub/cryptography_set_authenticated_data.cc",
+    "//starboard/shared/stub/cryptography_set_initialization_vector.cc",
+    "//starboard/shared/stub/cryptography_transform.cc",
+    "//starboard/shared/stub/drm_close_session.cc",
+    "//starboard/shared/stub/drm_create_system.cc",
+    "//starboard/shared/stub/drm_destroy_system.cc",
+    "//starboard/shared/stub/drm_generate_session_update_request.cc",
+    "//starboard/shared/stub/drm_get_metrics.cc",
+    "//starboard/shared/stub/drm_is_server_certificate_updatable.cc",
+    "//starboard/shared/stub/drm_update_server_certificate.cc",
+    "//starboard/shared/stub/drm_update_session.cc",
+    "//starboard/shared/stub/media_is_supported.cc",
+    "//starboard/shared/stub/media_set_audio_write_duration.cc",
+    "//starboard/shared/stub/microphone_close.cc",
+    "//starboard/shared/stub/microphone_create.cc",
+    "//starboard/shared/stub/microphone_destroy.cc",
+    "//starboard/shared/stub/microphone_get_available.cc",
+    "//starboard/shared/stub/microphone_is_sample_rate_supported.cc",
+    "//starboard/shared/stub/microphone_open.cc",
+    "//starboard/shared/stub/microphone_read.cc",
+    "//starboard/shared/stub/speech_synthesis_cancel.cc",
+    "//starboard/shared/stub/speech_synthesis_is_supported.cc",
+    "//starboard/shared/stub/speech_synthesis_speak.cc",
+    "//starboard/shared/stub/system_get_total_gpu_memory.cc",
+    "//starboard/shared/stub/system_get_used_gpu_memory.cc",
+    "//starboard/shared/stub/system_hide_splash_screen.cc",
+    "//starboard/shared/stub/system_network_is_disconnected.cc",
+    "//starboard/shared/stub/system_raise_platform_error.cc",
+    "//starboard/shared/stub/system_sign_with_certification_secret_key.cc",
+    "//starboard/shared/stub/ui_nav_get_interface.cc",
+    "//starboard/shared/stub/window_blur_on_screen_keyboard.cc",
+    "//starboard/shared/stub/window_focus_on_screen_keyboard.cc",
+    "//starboard/shared/stub/window_get_diagonal_size_in_inches.cc",
+    "//starboard/shared/stub/window_get_on_screen_keyboard_bounding_rect.cc",
+    "//starboard/shared/stub/window_hide_on_screen_keyboard.cc",
+    "//starboard/shared/stub/window_is_on_screen_keyboard_shown.cc",
+    "//starboard/shared/stub/window_on_screen_keyboard_is_supported.cc",
+    "//starboard/shared/stub/window_on_screen_keyboard_suggestions_supported.cc",
+    "//starboard/shared/stub/window_set_on_screen_keyboard_keep_focus.cc",
+    "//starboard/shared/stub/window_show_on_screen_keyboard.cc",
+    "//starboard/shared/stub/window_update_on_screen_keyboard_suggestions.cc",
+  ]
+
+  sources += common_player_sources
+
+  configs += [ "//starboard/build/config:starboard_implementation" ]
+
+  public_deps = [
+    ":starboard_base_symbolize",
+    "//starboard:starboard_headers_only",
+    "//starboard/common/",
+    "//starboard/shared/ffmpeg:ffmpeg_linked",
+    "//starboard/shared/starboard/media:media_util",
+    "//starboard/shared/starboard/player/filter:filter_based_player_sources",
+  ]
+  if (sb_is_evergreen_compatible) {
+    public_deps += [ "//starboard/elf_loader:evergreen_config" ]
+  }
+  if (sb_is_evergreen_compatible && !sb_evergreen_compatible_enable_lite) {
+    public_deps += [ "//starboard/loader_app:pending_restart" ]
+  }
+
+  deps = [
+    "//third_party/libevent",
+    "//third_party/opus",
+  ]
+  if (sb_evergreen_compatible_use_libunwind) {
+    deps += [ "//third_party/llvm-project/libunwind:unwind_starboard" ]
+  }
+}
+
+static_library("starboard_base_symbolize") {
+  sources = [
+    "//base/third_party/symbolize/demangle.cc",
+    "//base/third_party/symbolize/symbolize.cc",
+  ]
+
+  public_deps = [ "//starboard/elf_loader:evergreen_info" ]
+}
diff --git a/src/starboard/raspi/shared/platform_configuration/BUILD.gn b/src/starboard/raspi/shared/platform_configuration/BUILD.gn
new file mode 100644
index 0000000..fbbab79
--- /dev/null
+++ b/src/starboard/raspi/shared/platform_configuration/BUILD.gn
@@ -0,0 +1,195 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+declare_args() {
+  raspi_home = getenv("RASPI_HOME")
+}
+
+config("compiler_flags") {
+  cflags = []
+  cflags_c = []
+  cflags_cc = []
+  defines = []
+  ldflags = []
+
+  defines += [
+    # By default, <EGL/eglplatform.h> pulls in some X11 headers that have some
+    # nasty macros (|Status|, for example) that conflict with Chromium base.
+    "MESA_EGL_NO_X11_HEADERS",
+
+    # Cobalt on Linux flag
+    "COBALT_LINUX",
+    "__STDC_FORMAT_MACROS",  # so that we get PRI*
+    "_GNU_SOURCE=1",
+  ]
+
+  if (is_debug) {
+    cflags += [ "-O0" ]
+    cflags_cc += [ "-frtti" ]
+  } else if (is_devel) {
+    cflags += [ "-O2" ]
+    cflags_cc += [ "-frtti" ]
+  } else {
+    cflags += [ "-Wno-unused-but-set-variable" ]
+    cflags_cc += [ "-fno-rtti" ]
+  }
+
+  ldflags += [
+    "--sysroot=$raspi_home/busterroot",
+
+    # This is a quirk of Raspbian, these are required to link any GL-related
+    # libraries.
+    "-L$raspi_home/busterroot/opt/vc/lib",
+    "-Wl,-rpath=$raspi_home/busterroot/opt/vc/lib",
+    "-L$raspi_home/busterroot/usr/lib/arm-linux-gnueabihf",
+    "-Wl,-rpath=$raspi_home/busterroot/usr/lib/arm-linux-gnueabihf",
+    "-L$raspi_home/busterroot/lib/arm-linux-gnueabihf",
+    "-Wl,-rpath=$raspi_home/busterroot/lib/arm-linux-gnueabihf",
+
+    # Cleanup unused sections
+    "-Wl,-gc-sections",
+    "-Wl,--unresolved-symbols=ignore-in-shared-libs",
+  ]
+
+  cflags += [
+    # Generated by Audio Renderer and Audio Sink implementations.
+    "-Wno-reorder",
+
+    # Generated by code in the raspi/shared/open_max.
+    "-Wno-sign-compare",
+
+    # Generated by many starboard implementation files.
+    "-Wno-unused-parameter",
+    "-Wno-unused-variable",
+  ]
+
+  cflags += [
+    # Force char to be signed.
+    "-fsigned-char",
+
+    # Disable strict aliasing.
+    "-fno-strict-aliasing",
+
+    # Allow Skia"s SkVx.h to convert between vectors of different element
+    # types or number of subparts.
+    "-flax-vector-conversions",
+
+    # To support large files
+    "-D_FILE_OFFSET_BITS=64",
+
+    # Suppress some warnings that will be hard to fix.
+    "-Wno-unused-local-typedefs",
+    "-Wno-unused-result",
+    "-Wno-unused-function",
+    "-Wno-deprecated-declarations",
+    "-Wno-missing-field-initializers",
+    "-Wno-extra",
+    "-Wno-comment",  # Talk to my lawyer.
+    "-Wno-narrowing",
+    "-Wno-unknown-pragmas",
+    "-Wno-type-limits",  # TODO: We should actually look into these.
+
+    # It"s OK not to use some input parameters. Note that the order
+    # matters: Wall implies Wunused-parameter and Wno-unused-parameter
+    # has no effect if specified before Wall.
+    "-Wno-unused-parameter",
+
+    # Specify the sysroot with all your include dependencies.
+    "--sysroot=$raspi_home/busterroot",
+
+    # This is a quirk of Raspbian, these are required to include any
+    # GL-related headers.
+    "-I$raspi_home/busterroot/opt/vc/include",
+    "-I$raspi_home/busterroot/opt/vc/include/interface/vcos/pthreads",
+    "-I$raspi_home/busterroot/opt/vc/include/interface/vmcs_host/linux",
+    "-I$raspi_home/busterroot/usr/include/arm-linux-gnueabihf",
+  ]
+
+  if (!cobalt_fastbuild && (is_debug || is_devel)) {
+    cflags += [ "-g" ]
+  }
+
+  cflags_c += [ "-std=c11" ]
+  cflags_cc += [
+    "-std=gnu++14",
+    "-Wno-literal-suffix",
+  ]
+
+  if (sb_pedantic_warnings) {
+    cflags += [
+      "-Wall",
+      "-Wextra",
+      "-Wunreachable-code",
+
+      # Raspi toolchain is based off an old version of gcc, which
+      # falsely flags some code.  That same code does not warn with gcc 6.3.
+      # This decision should be revisited after raspi toolchain is upgraded.
+      "-Wno-maybe-uninitialized",
+
+      #TODO: Renable -Werror after fixing all warnings.
+      #"-Werror",
+      "-Wno-expansion-to-defined",
+      "-Wno-implicit-fallthrough",
+    ]
+  } else {
+    cflags += [
+      # Do not warn for implicit type conversions that may change a value.
+      "-Wno-conversion",
+    ]
+  }
+}
+
+config("platform_configuration") {
+  libs = [
+    "asound",
+    "avcodec",
+    "avformat",
+    "avutil",
+    ":libpthread.so.0",
+    "pthread",
+    "rt",
+    "openmaxil",
+    "bcm_host",
+    "vcos",
+    "vchiq_arm",
+    "brcmGLESv2",
+    "brcmEGL",
+
+    # Static libs must be last, to avoid __dlopen linker errors
+    "EGL_static",
+    "GLESv2_static",
+  ]
+
+  configs = [ "//starboard/raspi/shared/platform_configuration:compiler_flags" ]
+}
+
+config("speed") {
+  cflags = [
+    "-O2",
+
+    # Compile symbols in separate sections
+    "-ffunction-sections",
+    "-fdata-sections",
+  ]
+}
+
+config("size") {
+  cflags = [
+    "-Os",
+
+    # Compile symbols in separate sections
+    "-ffunction-sections",
+    "-fdata-sections",
+  ]
+}
diff --git a/src/starboard/raspi/shared/platform_configuration/configuration.gni b/src/starboard/raspi/shared/platform_configuration/configuration.gni
new file mode 100644
index 0000000..c96fc14
--- /dev/null
+++ b/src/starboard/raspi/shared/platform_configuration/configuration.gni
@@ -0,0 +1,20 @@
+# Copyright 2021 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//starboard/build/config/base_configuration.gni")
+
+sb_pedantic_warnings = true
+sb_static_contents_output_data_dir = "$root_out_dir/content"
+
+sabi_path = "//starboard/sabi/arm/hardfp/sabi-v$sb_api_version.json"
diff --git a/src/starboard/shared/starboard/player/filter/testing/adaptive_audio_decoder_test.cc b/src/starboard/shared/starboard/player/filter/testing/adaptive_audio_decoder_test.cc
index dbb1d9e..5127ae4 100644
--- a/src/starboard/shared/starboard/player/filter/testing/adaptive_audio_decoder_test.cc
+++ b/src/starboard/shared/starboard/player/filter/testing/adaptive_audio_decoder_test.cc
@@ -362,8 +362,8 @@
     return test_params;
   }
 
-  vector<const char*> supported_files =
-      GetSupportedAudioTestFiles(kExcludeHeaac, 6);
+  vector<const char*> supported_files = GetSupportedAudioTestFiles(
+      kExcludeHeaac, 6, "audiopassthrough=\"false\"");
 
   // Generate test cases. For example, we have |supported_files| [A, B, C].
   // Add tests A->A, A->B, A->C, B->A, B->B, B->C, C->A, C->B and C->C.
diff --git a/src/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc b/src/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc
index 74b8adf..00933f3 100644
--- a/src/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc
+++ b/src/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc
@@ -635,7 +635,10 @@
 INSTANTIATE_TEST_CASE_P(
     AudioDecoderTests,
     AudioDecoderTest,
-    Combine(ValuesIn(GetSupportedAudioTestFiles(kIncludeHeaac, 6)), Bool()));
+    Combine(ValuesIn(GetSupportedAudioTestFiles(kIncludeHeaac,
+                                                6,
+                                                "audiopassthrough=\"false\"")),
+            Bool()));
 
 }  // namespace
 }  // namespace testing
diff --git a/src/third_party/inspector_protocol/crdtp/json_platform.cc b/src/third_party/inspector_protocol/crdtp/json_platform.cc
index 59c8deb..21e26ce 100644
--- a/src/third_party/inspector_protocol/crdtp/json_platform.cc
+++ b/src/third_party/inspector_protocol/crdtp/json_platform.cc
@@ -16,6 +16,9 @@
 namespace json {
 namespace platform {
 bool StrToD(const char* str, double* result) {
+#if SB_IS(EVERGREEN)
+#error "The std::locale::classic() is not supported for Evergreen. Please use base::StringToDouble()."
+#endif
   std::istringstream is(str);
   is.imbue(std::locale::classic());
   is >> *result;
@@ -23,6 +26,9 @@
 }
 
 std::string DToStr(double value) {
+#if SB_IS(EVERGREEN)
+#error "The std::locale::classic() is not supported for Evergreen. Please use base::NumberToString()."
+#endif
   std::stringstream ss;
   ss.imbue(std::locale::classic());
   ss << value;
diff --git a/src/third_party/libevent/evdns.c b/src/third_party/libevent/evdns.c
index 05fe594..d72fdaa 100644
--- a/src/third_party/libevent/evdns.c
+++ b/src/third_party/libevent/evdns.c
@@ -783,7 +783,6 @@
 
 	for(;;) {
 		u8 label_len;
-		if (j >= length) return -1;
 		GET8(label_len);
 		if (!label_len) break;
 		if (label_len & 0xc0) {
@@ -804,6 +803,7 @@
 			*cp++ = '.';
 		}
 		if (cp + label_len >= end) return -1;
+		if (j + label_len > length) return -1;
 		memcpy(cp, packet + j, label_len);
 		cp += label_len;
 		j += label_len;
diff --git a/src/third_party/libxml/src/entities.c b/src/third_party/libxml/src/entities.c
index ea54cc3..4b41fe9 100644
--- a/src/third_party/libxml/src/entities.c
+++ b/src/third_party/libxml/src/entities.c
@@ -667,11 +667,25 @@
 	    } else {
 		/*
 		 * We assume we have UTF-8 input.
+		 * It must match either:
+		 *   110xxxxx 10xxxxxx
+		 *   1110xxxx 10xxxxxx 10xxxxxx
+		 *   11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+		 * That is:
+		 *   cur[0] is 11xxxxxx
+		 *   cur[1] is 10xxxxxx
+		 *   cur[2] is 10xxxxxx if cur[0] is 111xxxxx
+		 *   cur[3] is 10xxxxxx if cur[0] is 1111xxxx
+		 *   cur[0] is not 11111xxx
 		 */
 		char buf[11], *ptr;
 		int val = 0, l = 1;
 
-		if (*cur < 0xC0) {
+		if (((cur[0] & 0xC0) != 0xC0) ||
+		    ((cur[1] & 0xC0) != 0x80) ||
+		    (((cur[0] & 0xE0) == 0xE0) && ((cur[2] & 0xC0) != 0x80)) ||
+		    (((cur[0] & 0xF0) == 0xF0) && ((cur[3] & 0xC0) != 0x80)) ||
+		    (((cur[0] & 0xF8) == 0xF8))) {
 		    xmlEntitiesErr(XML_CHECK_NOT_UTF8,
 			    "xmlEncodeEntities: input not UTF-8");
 		    if (doc != NULL)
diff --git a/src/third_party/skia/src/sksl/SkSLString.cpp b/src/third_party/skia/src/sksl/SkSLString.cpp
index 5cabe9f..b5a2605 100644
--- a/src/third_party/skia/src/sksl/SkSLString.cpp
+++ b/src/third_party/skia/src/sksl/SkSLString.cpp
@@ -16,6 +16,8 @@
 #include <stdlib.h>
 #include <string>
 
+#include "base/strings/string_number_conversions.h"
+
 #if defined(STARBOARD)
 #include "starboard/client_porting/poem/string_poem.h"
 #endif
@@ -229,6 +231,24 @@
 }
 
 String to_string(double value) {
+#if defined(STARBOARD)
+    std::string s = base::NumberToString(value);
+    bool needsDotZero = true;
+    for (int i = s.size() - 1; i >= 0; --i) {
+        char c = s[i];
+        if (c == '.' || c == 'e') {
+            needsDotZero = false;
+            break;
+        }
+    }
+    if (needsDotZero) {
+        s += ".0";
+    }
+    if (s.size() > 0 && s[0] == '.') {
+      s = "0" + s;
+    }
+    return String(s.c_str());
+#else
     std::stringstream buffer;
     buffer.imbue(std::locale::classic());
     buffer.precision(17);
@@ -246,6 +266,7 @@
         buffer << ".0";
     }
     return String(buffer.str().c_str());
+#endif
 }
 
 SKSL_INT stoi(const String& s) {
@@ -258,6 +279,12 @@
 }
 
 SKSL_FLOAT stod(const String& s) {
+#if defined(STARBOARD)
+    double d;
+    bool res= base::StringToDouble(s.c_str(), &d);
+    SkASSERT(res);
+    return d;
+#else
     double result;
     std::string str(s.c_str(), s.size());
     std::stringstream buffer(str);
@@ -265,6 +292,7 @@
     buffer >> result;
     SkASSERT(!buffer.fail());
     return result;
+#endif
 }
 
 long stol(const String& s) {
diff --git a/src/third_party/zlib/BUILD.gn b/src/third_party/zlib/BUILD.gn
index abcbcb4..4c5e6b4 100644
--- a/src/third_party/zlib/BUILD.gn
+++ b/src/third_party/zlib/BUILD.gn
@@ -57,7 +57,9 @@
     ]
     if (!is_debug) {
       # Use optimize_speed (-O3) to output the _smallest_ code.
-      configs -= [ "//build/config/compiler:default_optimization" ]
+      if(!is_starboard) {
+        configs -= [ "//build/config/compiler:default_optimization" ]
+      }
       configs += [ "//build/config/compiler:optimize_speed" ]
     }
   }
@@ -76,7 +78,7 @@
     # Disabled for iPhone, as described in DDI0487C_a_armv8_arm:
     #  "All implementations of the ARMv8.1 architecture are required to
     #   implement the CRC32* instructions. These are optional in ARMv8.0."
-    if (!is_ios) {
+    if (!is_ios && arm_version >= 8) {
       defines = [ "CRC32_ARMV8_CRC32" ]
       if (is_android) {
         defines += [ "ARMV8_OS_ANDROID" ]
@@ -121,7 +123,9 @@
       ]
 
       if (!is_debug) {
-        configs -= [ "//build/config/compiler:default_optimization" ]
+        if(!is_starboard) {
+          configs -= [ "//build/config/compiler:default_optimization" ]
+        }
         configs += [ "//build/config/compiler:optimize_speed" ]
       }
     }
@@ -174,7 +178,9 @@
     if (use_arm_neon_optimizations && !is_debug) {
       # Here we trade better performance on newer/bigger ARMv8 cores
       # for less perf on ARMv7, per crbug.com/772870#c40
-      configs -= [ "//build/config/compiler:default_optimization" ]
+      if(!is_starboard) {
+        configs -= [ "//build/config/compiler:default_optimization" ]
+      }
       configs += [ "//build/config/compiler:optimize_speed" ]
     }
   }
@@ -312,7 +318,10 @@
       cflags_c += [ "-Wno-missing-braces" ]
       deps += [ ":zlib_crc32_simd" ]
     } else if (use_arm_neon_optimizations) {
-      sources += [ "contrib/optimizations/slide_hash_neon.h" ]
+      sources += [
+        "contrib/optimizations/slide_hash_neon.c",
+        "contrib/optimizations/slide_hash_neon.h",
+      ]
       deps += [ ":zlib_arm_crc32" ]
     }
   } else {
diff --git a/src/tools/format_ninja.py b/src/tools/format_ninja.py
index b0d1ba0..f18e622 100644
--- a/src/tools/format_ninja.py
+++ b/src/tools/format_ninja.py
@@ -33,6 +33,7 @@
 from typing import List, Tuple
 
 _ACTION_COMPONENTS = ['directory', 'command', 'file', 'output']
+STRIP = 0
 
 
 def make_path_absolute(path: str, directory: str) -> str:
@@ -43,6 +44,9 @@
 
 
 def remove_directory_path(path: str, directory: str) -> str:
+  if STRIP :
+    dirsplit = directory.split(os.path.sep)
+    directory = os.path.sep.join(dirsplit[:-(STRIP)])
   if os.path.commonpath([path, directory]) != directory:
     return path
 
@@ -113,7 +117,10 @@
   parser = argparse.ArgumentParser()
   parser.add_argument('json_filename', type=str)
   parser.add_argument('-o', '--output', type=str)
+  parser.add_argument('-p', '--strip', type=int)
   args = parser.parse_args()
+  if args.strip:
+    STRIP = int(args.strip)
   output = args.output if args.output else 'normalized_' + os.path.basename(
       args.json_filename)
   main(args.json_filename, output)