Merge "transcoding: fix nullptr in TranscodingSessionController test"
diff --git a/media/codec2/vndk/C2AllocatorBlob.cpp b/media/codec2/vndk/C2AllocatorBlob.cpp
index 565137c..6340cba 100644
--- a/media/codec2/vndk/C2AllocatorBlob.cpp
+++ b/media/codec2/vndk/C2AllocatorBlob.cpp
@@ -17,6 +17,8 @@
 // #define LOG_NDEBUG 0
 #define LOG_TAG "C2AllocatorBlob"
 
+#include <set>
+
 #include <C2AllocatorBlob.h>
 #include <C2PlatformSupport.h>
 
@@ -67,6 +69,10 @@
 private:
     const std::shared_ptr<C2GraphicAllocation> mGraphicAllocation;
     const C2Allocator::id_t mAllocatorId;
+
+    std::mutex mMapLock;
+    std::multiset<std::pair<size_t, size_t>> mMappedOffsetSize;
+    uint8_t *mMappedAddr;
 };
 
 C2AllocationBlob::C2AllocationBlob(
@@ -74,20 +80,74 @@
         C2Allocator::id_t allocatorId)
       : C2LinearAllocation(capacity),
         mGraphicAllocation(std::move(graphicAllocation)),
-        mAllocatorId(allocatorId) {}
+        mAllocatorId(allocatorId),
+        mMappedAddr(nullptr) {}
 
-C2AllocationBlob::~C2AllocationBlob() {}
+C2AllocationBlob::~C2AllocationBlob() {
+    if (mMappedAddr) {
+        C2Rect rect(capacity(), kLinearBufferHeight);
+        mGraphicAllocation->unmap(&mMappedAddr, rect, nullptr);
+    }
+}
 
 c2_status_t C2AllocationBlob::map(size_t offset, size_t size, C2MemoryUsage usage,
                                   C2Fence* fence, void** addr /* nonnull */) {
+    *addr = nullptr;
+    if (size > capacity() || offset > capacity() || offset > capacity() - size) {
+        ALOGV("C2AllocationBlob: map: bad offset / size: offset=%zu size=%zu capacity=%u",
+                offset, size, capacity());
+        return C2_BAD_VALUE;
+    }
+    std::unique_lock<std::mutex> lock(mMapLock);
+    if (mMappedAddr) {
+        *addr = mMappedAddr + offset;
+        mMappedOffsetSize.insert({offset, size});
+        ALOGV("C2AllocationBlob: mapped from existing mapping: offset=%zu size=%zu capacity=%u",
+                offset, size, capacity());
+        return C2_OK;
+    }
     C2PlanarLayout layout;
-    C2Rect rect = C2Rect(size, kLinearBufferHeight).at(offset, 0u);
-    return mGraphicAllocation->map(rect, usage, fence, &layout, reinterpret_cast<uint8_t**>(addr));
+    C2Rect rect = C2Rect(capacity(), kLinearBufferHeight);
+    c2_status_t err = mGraphicAllocation->map(rect, usage, fence, &layout, &mMappedAddr);
+    if (err != C2_OK) {
+        ALOGV("C2AllocationBlob: map failed: offset=%zu size=%zu capacity=%u err=%d",
+                offset, size, capacity(), err);
+        mMappedAddr = nullptr;
+        return err;
+    }
+    *addr = mMappedAddr + offset;
+    mMappedOffsetSize.insert({offset, size});
+    ALOGV("C2AllocationBlob: new map succeeded: offset=%zu size=%zu capacity=%u",
+            offset, size, capacity());
+    return C2_OK;
 }
 
 c2_status_t C2AllocationBlob::unmap(void* addr, size_t size, C2Fence* fenceFd) {
-    C2Rect rect(size, kLinearBufferHeight);
-    return mGraphicAllocation->unmap(reinterpret_cast<uint8_t**>(&addr), rect, fenceFd);
+    std::unique_lock<std::mutex> lock(mMapLock);
+    uint8_t *u8Addr = static_cast<uint8_t *>(addr);
+    if (u8Addr < mMappedAddr || mMappedAddr + capacity() < u8Addr + size) {
+        ALOGV("C2AllocationBlob: unmap: Bad addr / size: addr=%p size=%zu capacity=%u",
+                addr, size, capacity());
+        return C2_BAD_VALUE;
+    }
+    auto it = mMappedOffsetSize.find(std::make_pair(u8Addr - mMappedAddr, size));
+    if (it == mMappedOffsetSize.end()) {
+        ALOGV("C2AllocationBlob: unrecognized map: addr=%p size=%zu capacity=%u",
+                addr, size, capacity());
+        return C2_BAD_VALUE;
+    }
+    mMappedOffsetSize.erase(it);
+    if (!mMappedOffsetSize.empty()) {
+        ALOGV("C2AllocationBlob: still maintain mapping: addr=%p size=%zu capacity=%u",
+                addr, size, capacity());
+        return C2_OK;
+    }
+    C2Rect rect(capacity(), kLinearBufferHeight);
+    c2_status_t err = mGraphicAllocation->unmap(&mMappedAddr, rect, fenceFd);
+    ALOGV("C2AllocationBlob: last unmap: addr=%p size=%zu capacity=%u err=%d",
+            addr, size, capacity(), err);
+    mMappedAddr = nullptr;
+    return err;
 }
 
 /* ====================================== BLOB ALLOCATOR ====================================== */
diff --git a/media/libaudioclient/AidlConversion.cpp b/media/libaudioclient/AidlConversion.cpp
index 8fb836d..f11f184 100644
--- a/media/libaudioclient/AidlConversion.cpp
+++ b/media/libaudioclient/AidlConversion.cpp
@@ -110,29 +110,6 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
-// Utilities for working with AIDL unions.
-// UNION_GET(obj, fieldname) returns a ConversionResult<T> containing either the strongly-typed
-//   value of the respective field, or BAD_VALUE if the union is not set to the requested field.
-// UNION_SET(obj, fieldname, value) sets the requested field to the given value.
-
-template<typename T, typename T::Tag tag>
-using UnionFieldType = std::decay_t<decltype(std::declval<T>().template get<tag>())>;
-
-template<typename T, typename T::Tag tag>
-ConversionResult<UnionFieldType<T, tag>> unionGetField(const T& u) {
-    if (u.getTag() != tag) {
-        return unexpected(BAD_VALUE);
-    }
-    return u.template get<tag>();
-}
-
-#define UNION_GET(u, field) \
-    unionGetField<std::decay_t<decltype(u)>, std::decay_t<decltype(u)>::Tag::field>(u)
-
-#define UNION_SET(u, field, value) \
-    (u).set<std::decay_t<decltype(u)>::Tag::field>(value)
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
 
 enum class Direction {
     INPUT, OUTPUT
@@ -483,7 +460,7 @@
     return static_cast<media::audio::common::AudioFormat>(legacy);
 }
 
-ConversionResult<int> aidl2legacy_AudioGainMode_int(media::AudioGainMode aidl) {
+ConversionResult<audio_gain_mode_t> aidl2legacy_AudioGainMode_audio_gain_mode_t(media::AudioGainMode aidl) {
     switch (aidl) {
         case media::AudioGainMode::JOINT:
             return AUDIO_GAIN_MODE_JOINT;
@@ -496,7 +473,7 @@
     }
 }
 
-ConversionResult<media::AudioGainMode> legacy2aidl_int_AudioGainMode(int legacy) {
+ConversionResult<media::AudioGainMode> legacy2aidl_audio_gain_mode_t_AudioGainMode(audio_gain_mode_t legacy) {
     switch (legacy) {
         case AUDIO_GAIN_MODE_JOINT:
             return media::AudioGainMode::JOINT;
@@ -509,20 +486,20 @@
     }
 }
 
-ConversionResult<audio_gain_mode_t> aidl2legacy_int32_t_audio_gain_mode_t(int32_t aidl) {
-    return convertBitmask<audio_gain_mode_t, int32_t, int, media::AudioGainMode>(
-            aidl, aidl2legacy_AudioGainMode_int,
+ConversionResult<audio_gain_mode_t> aidl2legacy_int32_t_audio_gain_mode_t_mask(int32_t aidl) {
+    return convertBitmask<audio_gain_mode_t, int32_t, audio_gain_mode_t, media::AudioGainMode>(
+            aidl, aidl2legacy_AudioGainMode_audio_gain_mode_t,
             // AudioGainMode is index-based.
             index2enum_index<media::AudioGainMode>,
             // AUDIO_GAIN_MODE_* constants are mask-based.
-            enumToMask_bitmask<audio_gain_mode_t, int>);
+            enumToMask_bitmask<audio_gain_mode_t, audio_gain_mode_t>);
 }
 
-ConversionResult<int32_t> legacy2aidl_audio_gain_mode_t_int32_t(audio_gain_mode_t legacy) {
-    return convertBitmask<int32_t, audio_gain_mode_t, media::AudioGainMode, int>(
-            legacy, legacy2aidl_int_AudioGainMode,
+ConversionResult<int32_t> legacy2aidl_audio_gain_mode_t_mask_int32_t(audio_gain_mode_t legacy) {
+    return convertBitmask<int32_t, audio_gain_mode_t, media::AudioGainMode, audio_gain_mode_t>(
+            legacy, legacy2aidl_audio_gain_mode_t_AudioGainMode,
             // AUDIO_GAIN_MODE_* constants are mask-based.
-            index2enum_bitmask<int>,
+            index2enum_bitmask<audio_gain_mode_t>,
             // AudioGainMode is index-based.
             enumToMask_index<int32_t, media::AudioGainMode>);
 }
@@ -541,7 +518,7 @@
         const media::AudioGainConfig& aidl, media::AudioPortRole role, media::AudioPortType type) {
     audio_gain_config legacy;
     legacy.index = VALUE_OR_RETURN(convertIntegral<int>(aidl.index));
-    legacy.mode = VALUE_OR_RETURN(aidl2legacy_int32_t_audio_gain_mode_t(aidl.mode));
+    legacy.mode = VALUE_OR_RETURN(aidl2legacy_int32_t_audio_gain_mode_t_mask(aidl.mode));
     legacy.channel_mask =
             VALUE_OR_RETURN(aidl2legacy_int32_t_audio_channel_mask_t(aidl.channelMask));
     const bool isInput = VALUE_OR_RETURN(direction(role, type)) == Direction::INPUT;
@@ -563,7 +540,7 @@
         const audio_gain_config& legacy, audio_port_role_t role, audio_port_type_t type) {
     media::AudioGainConfig aidl;
     aidl.index = VALUE_OR_RETURN(convertIntegral<int32_t>(legacy.index));
-    aidl.mode = VALUE_OR_RETURN(legacy2aidl_audio_gain_mode_t_int32_t(legacy.mode));
+    aidl.mode = VALUE_OR_RETURN(legacy2aidl_audio_gain_mode_t_mask_int32_t(legacy.mode));
     aidl.channelMask =
             VALUE_OR_RETURN(legacy2aidl_audio_channel_mask_t_int32_t(legacy.channel_mask));
     const bool isInput = VALUE_OR_RETURN(direction(role, type)) == Direction::INPUT;
@@ -977,7 +954,7 @@
     switch (role) {
         case media::AudioPortRole::NONE:
             // Just verify that the union is empty.
-            VALUE_OR_RETURN(UNION_GET(aidl, nothing));
+            VALUE_OR_RETURN(UNION_GET(aidl, unspecified));
             break;
 
         case media::AudioPortRole::SOURCE:
@@ -1004,7 +981,7 @@
 
     switch (role) {
         case AUDIO_PORT_ROLE_NONE:
-            UNION_SET(aidl, nothing, false);
+            UNION_SET(aidl, unspecified, false);
             break;
         case AUDIO_PORT_ROLE_SOURCE:
             // This is not a bug. A SOURCE role corresponds to the stream field.
@@ -1061,12 +1038,10 @@
         const media::AudioPortConfigExt& aidl, media::AudioPortType type,
         media::AudioPortRole role) {
     audio_port_config_ext legacy;
-    // Our way of representing a union in AIDL is to have multiple vectors and require that at most
-    // one of the them has size 1 and the rest are empty.
     switch (type) {
         case media::AudioPortType::NONE:
             // Just verify that the union is empty.
-            VALUE_OR_RETURN(UNION_GET(aidl, nothing));
+            VALUE_OR_RETURN(UNION_GET(aidl, unspecified));
             break;
         case media::AudioPortType::DEVICE:
             legacy.device = VALUE_OR_RETURN(
@@ -1092,7 +1067,7 @@
 
     switch (type) {
         case AUDIO_PORT_TYPE_NONE:
-            UNION_SET(aidl, nothing, false);
+            UNION_SET(aidl, unspecified, false);
             break;
         case AUDIO_PORT_TYPE_DEVICE:
             UNION_SET(aidl, device,
@@ -1829,4 +1804,282 @@
             enumToMask_index<int32_t, media::AudioEncapsulationMetadataType>);
 }
 
+ConversionResult<audio_mix_latency_class_t>
+aidl2legacy_AudioMixLatencyClass_audio_mix_latency_class_t(
+        media::AudioMixLatencyClass aidl) {
+    switch (aidl) {
+        case media::AudioMixLatencyClass::LOW:
+            return AUDIO_LATENCY_LOW;
+        case media::AudioMixLatencyClass::NORMAL:
+            return AUDIO_LATENCY_NORMAL;
+    }
+    return unexpected(BAD_VALUE);
+}
+
+ConversionResult<media::AudioMixLatencyClass>
+legacy2aidl_audio_mix_latency_class_t_AudioMixLatencyClass(
+        audio_mix_latency_class_t legacy) {
+    switch (legacy) {
+        case AUDIO_LATENCY_LOW:
+            return media::AudioMixLatencyClass::LOW;
+        case AUDIO_LATENCY_NORMAL:
+            return media::AudioMixLatencyClass::NORMAL;
+    }
+    return unexpected(BAD_VALUE);
+}
+
+ConversionResult<audio_port_device_ext>
+aidl2legacy_AudioPortDeviceExt_audio_port_device_ext(const media::AudioPortDeviceExt& aidl) {
+    audio_port_device_ext legacy;
+    legacy.hw_module = VALUE_OR_RETURN(aidl2legacy_int32_t_audio_module_handle_t(aidl.hwModule));
+    legacy.type = VALUE_OR_RETURN(aidl2legacy_int32_t_audio_devices_t(aidl.device.type));
+    RETURN_IF_ERROR(
+            aidl2legacy_string(aidl.device.address, legacy.address, sizeof(legacy.address)));
+    legacy.encapsulation_modes = VALUE_OR_RETURN(
+            aidl2legacy_AudioEncapsulationMode_mask(aidl.encapsulationModes));
+    legacy.encapsulation_metadata_types = VALUE_OR_RETURN(
+            aidl2legacy_AudioEncapsulationMetadataType_mask(aidl.encapsulationMetadataTypes));
+    return legacy;
+}
+
+ConversionResult<media::AudioPortDeviceExt>
+legacy2aidl_audio_port_device_ext_AudioPortDeviceExt(const audio_port_device_ext& legacy) {
+    media::AudioPortDeviceExt aidl;
+    aidl.hwModule = VALUE_OR_RETURN(legacy2aidl_audio_module_handle_t_int32_t(legacy.hw_module));
+    aidl.device.type = VALUE_OR_RETURN(legacy2aidl_audio_devices_t_int32_t(legacy.type));
+    aidl.device.address = VALUE_OR_RETURN(
+            legacy2aidl_string(legacy.address, sizeof(legacy.address)));
+    aidl.encapsulationModes = VALUE_OR_RETURN(
+            legacy2aidl_AudioEncapsulationMode_mask(legacy.encapsulation_modes));
+    aidl.encapsulationMetadataTypes = VALUE_OR_RETURN(
+            legacy2aidl_AudioEncapsulationMetadataType_mask(legacy.encapsulation_metadata_types));
+    return aidl;
+}
+
+ConversionResult<audio_port_mix_ext>
+aidl2legacy_AudioPortMixExt_audio_port_mix_ext(const media::AudioPortMixExt& aidl) {
+    audio_port_mix_ext legacy;
+    legacy.hw_module = VALUE_OR_RETURN(aidl2legacy_int32_t_audio_module_handle_t(aidl.hwModule));
+    legacy.handle = VALUE_OR_RETURN(aidl2legacy_int32_t_audio_io_handle_t(aidl.handle));
+    legacy.latency_class = VALUE_OR_RETURN(
+            aidl2legacy_AudioMixLatencyClass_audio_mix_latency_class_t(aidl.latencyClass));
+    return legacy;
+}
+
+ConversionResult<media::AudioPortMixExt>
+legacy2aidl_audio_port_mix_ext_AudioPortMixExt(const audio_port_mix_ext& legacy) {
+    media::AudioPortMixExt aidl;
+    aidl.hwModule = VALUE_OR_RETURN(legacy2aidl_audio_module_handle_t_int32_t(legacy.hw_module));
+    aidl.handle = VALUE_OR_RETURN(legacy2aidl_audio_io_handle_t_int32_t(legacy.handle));
+    aidl.latencyClass = VALUE_OR_RETURN(
+            legacy2aidl_audio_mix_latency_class_t_AudioMixLatencyClass(legacy.latency_class));
+    return aidl;
+}
+
+ConversionResult<audio_port_session_ext>
+aidl2legacy_AudioPortSessionExt_audio_port_session_ext(const media::AudioPortSessionExt& aidl) {
+    audio_port_session_ext legacy;
+    legacy.session = VALUE_OR_RETURN(aidl2legacy_int32_t_audio_session_t(aidl.session));
+    return legacy;
+}
+
+ConversionResult<media::AudioPortSessionExt>
+legacy2aidl_audio_port_session_ext_AudioPortSessionExt(const audio_port_session_ext& legacy) {
+    media::AudioPortSessionExt aidl;
+    aidl.session = VALUE_OR_RETURN(legacy2aidl_audio_session_t_int32_t(legacy.session));
+    return aidl;
+}
+
+// This type is unnamed in the original definition, thus we name it here.
+using audio_port_v7_ext = decltype(audio_port_v7::ext);
+
+ConversionResult<audio_port_v7_ext> aidl2legacy_AudioPortExt(
+        const media::AudioPortExt& aidl, media::AudioPortType type) {
+    audio_port_v7_ext legacy;
+    switch (type) {
+        case media::AudioPortType::NONE:
+            // Just verify that the union is empty.
+            VALUE_OR_RETURN(UNION_GET(aidl, unspecified));
+            break;
+        case media::AudioPortType::DEVICE:
+            legacy.device = VALUE_OR_RETURN(
+                    aidl2legacy_AudioPortDeviceExt_audio_port_device_ext(
+                            VALUE_OR_RETURN(UNION_GET(aidl, device))));
+            break;
+        case media::AudioPortType::MIX:
+            legacy.mix = VALUE_OR_RETURN(
+                    aidl2legacy_AudioPortMixExt_audio_port_mix_ext(
+                            VALUE_OR_RETURN(UNION_GET(aidl, mix))));
+            break;
+        case media::AudioPortType::SESSION:
+            legacy.session = VALUE_OR_RETURN(aidl2legacy_AudioPortSessionExt_audio_port_session_ext(
+                    VALUE_OR_RETURN(UNION_GET(aidl, session))));
+            break;
+        default:
+            LOG_ALWAYS_FATAL("Shouldn't get here");
+    }
+    return legacy;
+}
+
+ConversionResult<media::AudioPortExt> legacy2aidl_AudioPortExt(
+        const audio_port_v7_ext& legacy, audio_port_type_t type) {
+    media::AudioPortExt aidl;
+    switch (type) {
+        case AUDIO_PORT_TYPE_NONE:
+            UNION_SET(aidl, unspecified, false);
+            break;
+        case AUDIO_PORT_TYPE_DEVICE:
+            UNION_SET(aidl, device,
+                      VALUE_OR_RETURN(
+                              legacy2aidl_audio_port_device_ext_AudioPortDeviceExt(legacy.device)));
+            break;
+        case AUDIO_PORT_TYPE_MIX:
+            UNION_SET(aidl, mix,
+                      VALUE_OR_RETURN(legacy2aidl_audio_port_mix_ext_AudioPortMixExt(legacy.mix)));
+            break;
+        case AUDIO_PORT_TYPE_SESSION:
+            UNION_SET(aidl, session,
+                      VALUE_OR_RETURN(legacy2aidl_audio_port_session_ext_AudioPortSessionExt(
+                              legacy.session)));
+            break;
+        default:
+            LOG_ALWAYS_FATAL("Shouldn't get here");
+    }
+    return aidl;
+}
+
+ConversionResult<audio_profile>
+aidl2legacy_AudioProfile_audio_profile(const media::AudioProfile& aidl) {
+    audio_profile legacy;
+    legacy.format = VALUE_OR_RETURN(aidl2legacy_AudioFormat_audio_format_t(aidl.format));
+
+    if (aidl.samplingRates.size() > std::size(legacy.sample_rates)) {
+        return unexpected(BAD_VALUE);
+    }
+    RETURN_IF_ERROR(
+            convertRange(aidl.samplingRates.begin(), aidl.samplingRates.end(), legacy.sample_rates,
+                         convertIntegral<int32_t, unsigned int>));
+    legacy.num_sample_rates = aidl.samplingRates.size();
+
+    if (aidl.channelMasks.size() > std::size(legacy.channel_masks)) {
+        return unexpected(BAD_VALUE);
+    }
+    RETURN_IF_ERROR(
+            convertRange(aidl.channelMasks.begin(), aidl.channelMasks.end(), legacy.channel_masks,
+                         aidl2legacy_int32_t_audio_channel_mask_t));
+    legacy.num_channel_masks = aidl.channelMasks.size();
+    return legacy;
+}
+
+ConversionResult<media::AudioProfile>
+legacy2aidl_audio_profile_AudioProfile(const audio_profile& legacy) {
+    media::AudioProfile aidl;
+    aidl.format = VALUE_OR_RETURN(legacy2aidl_audio_format_t_AudioFormat(legacy.format));
+
+    if (legacy.num_sample_rates > std::size(legacy.sample_rates)) {
+        return unexpected(BAD_VALUE);
+    }
+    RETURN_IF_ERROR(
+            convertRange(legacy.sample_rates, legacy.sample_rates + legacy.num_sample_rates,
+                         std::back_inserter(aidl.samplingRates),
+                         convertIntegral<unsigned int, int32_t>));
+
+    if (legacy.num_channel_masks > std::size(legacy.channel_masks)) {
+        return unexpected(BAD_VALUE);
+    }
+    RETURN_IF_ERROR(
+            convertRange(legacy.channel_masks, legacy.channel_masks + legacy.num_channel_masks,
+                         std::back_inserter(aidl.channelMasks),
+                         legacy2aidl_audio_channel_mask_t_int32_t));
+    return aidl;
+}
+
+ConversionResult<audio_gain>
+aidl2legacy_AudioGain_audio_gain(const media::AudioGain& aidl) {
+    audio_gain legacy;
+    legacy.mode = VALUE_OR_RETURN(aidl2legacy_int32_t_audio_gain_mode_t_mask(aidl.mode));
+    legacy.channel_mask = VALUE_OR_RETURN(
+            aidl2legacy_int32_t_audio_channel_mask_t(aidl.channelMask));
+    legacy.min_value = VALUE_OR_RETURN(convertIntegral<int>(aidl.minValue));
+    legacy.max_value = VALUE_OR_RETURN(convertIntegral<int>(aidl.maxValue));
+    legacy.default_value = VALUE_OR_RETURN(convertIntegral<int>(aidl.defaultValue));
+    legacy.step_value = VALUE_OR_RETURN(convertIntegral<unsigned int>(aidl.stepValue));
+    legacy.min_ramp_ms = VALUE_OR_RETURN(convertIntegral<unsigned int>(aidl.minRampMs));
+    legacy.max_ramp_ms = VALUE_OR_RETURN(convertIntegral<unsigned int>(aidl.maxRampMs));
+    return legacy;
+}
+
+ConversionResult<media::AudioGain>
+legacy2aidl_audio_gain_AudioGain(const audio_gain& legacy) {
+    media::AudioGain aidl;
+    aidl.mode = VALUE_OR_RETURN(legacy2aidl_audio_gain_mode_t_mask_int32_t(legacy.mode));
+    aidl.channelMask = VALUE_OR_RETURN(
+            legacy2aidl_audio_channel_mask_t_int32_t(legacy.channel_mask));
+    aidl.minValue = VALUE_OR_RETURN(convertIntegral<int32_t>(legacy.min_value));
+    aidl.maxValue = VALUE_OR_RETURN(convertIntegral<int32_t>(legacy.max_value));
+    aidl.defaultValue = VALUE_OR_RETURN(convertIntegral<int32_t>(legacy.default_value));
+    aidl.stepValue = VALUE_OR_RETURN(convertIntegral<int32_t>(legacy.step_value));
+    aidl.minRampMs = VALUE_OR_RETURN(convertIntegral<int32_t>(legacy.min_ramp_ms));
+    aidl.maxRampMs = VALUE_OR_RETURN(convertIntegral<int32_t>(legacy.max_ramp_ms));
+    return aidl;
+}
+
+ConversionResult<audio_port_v7>
+aidl2legacy_AudioPort_audio_port_v7(const media::AudioPort& aidl) {
+    audio_port_v7 legacy;
+    legacy.id = VALUE_OR_RETURN(aidl2legacy_int32_t_audio_port_handle_t(aidl.id));
+    legacy.role = VALUE_OR_RETURN(aidl2legacy_AudioPortRole_audio_port_role_t(aidl.role));
+    legacy.type = VALUE_OR_RETURN(aidl2legacy_AudioPortType_audio_port_type_t(aidl.type));
+    RETURN_IF_ERROR(aidl2legacy_string(aidl.name, legacy.name, sizeof(legacy.name)));
+
+    if (aidl.profiles.size() > std::size(legacy.audio_profiles)) {
+        return unexpected(BAD_VALUE);
+    }
+    RETURN_IF_ERROR(convertRange(aidl.profiles.begin(), aidl.profiles.end(), legacy.audio_profiles,
+                                 aidl2legacy_AudioProfile_audio_profile));
+    legacy.num_audio_profiles = aidl.profiles.size();
+
+    if (aidl.gains.size() > std::size(legacy.gains)) {
+        return unexpected(BAD_VALUE);
+    }
+    RETURN_IF_ERROR(convertRange(aidl.gains.begin(), aidl.gains.end(), legacy.gains,
+                                 aidl2legacy_AudioGain_audio_gain));
+    legacy.num_gains = aidl.gains.size();
+
+    legacy.active_config = VALUE_OR_RETURN(
+            aidl2legacy_AudioPortConfig_audio_port_config(aidl.activeConfig));
+    legacy.ext = VALUE_OR_RETURN(aidl2legacy_AudioPortExt(aidl.ext, aidl.type));
+    return legacy;
+}
+
+ConversionResult<media::AudioPort>
+legacy2aidl_audio_port_v7_AudioPort(const audio_port_v7& legacy) {
+    media::AudioPort aidl;
+    aidl.id = VALUE_OR_RETURN(legacy2aidl_audio_port_handle_t_int32_t(legacy.id));
+    aidl.role = VALUE_OR_RETURN(legacy2aidl_audio_port_role_t_AudioPortRole(legacy.role));
+    aidl.type = VALUE_OR_RETURN(legacy2aidl_audio_port_type_t_AudioPortType(legacy.type));
+    aidl.name = VALUE_OR_RETURN(legacy2aidl_string(legacy.name, sizeof(legacy.name)));
+
+    if (legacy.num_audio_profiles > std::size(legacy.audio_profiles)) {
+        return unexpected(BAD_VALUE);
+    }
+    RETURN_IF_ERROR(
+            convertRange(legacy.audio_profiles, legacy.audio_profiles + legacy.num_audio_profiles,
+                         std::back_inserter(aidl.profiles),
+                         legacy2aidl_audio_profile_AudioProfile));
+
+    if (legacy.num_gains > std::size(legacy.gains)) {
+        return unexpected(BAD_VALUE);
+    }
+    RETURN_IF_ERROR(
+            convertRange(legacy.gains, legacy.gains + legacy.num_gains,
+                         std::back_inserter(aidl.gains),
+                         legacy2aidl_audio_gain_AudioGain));
+
+    aidl.activeConfig = VALUE_OR_RETURN(
+            legacy2aidl_audio_port_config_AudioPortConfig(legacy.active_config));
+    aidl.ext = VALUE_OR_RETURN(legacy2aidl_AudioPortExt(legacy.ext, legacy.type));
+    return aidl;
+}
+
 }  // namespace android
diff --git a/media/libaudioclient/Android.bp b/media/libaudioclient/Android.bp
index db1a2a3..fa67898 100644
--- a/media/libaudioclient/Android.bp
+++ b/media/libaudioclient/Android.bp
@@ -288,6 +288,7 @@
         "aidl/android/media/AudioIoConfigEvent.aidl",
         "aidl/android/media/AudioIoDescriptor.aidl",
         "aidl/android/media/AudioIoFlags.aidl",
+        "aidl/android/media/AudioMixLatencyClass.aidl",
         "aidl/android/media/AudioMode.aidl",
         "aidl/android/media/AudioOffloadInfo.aidl",
         "aidl/android/media/AudioOutputFlags.aidl",
@@ -300,7 +301,11 @@
         "aidl/android/media/AudioPortConfigMixExt.aidl",
         "aidl/android/media/AudioPortConfigMixExtUseCase.aidl",
         "aidl/android/media/AudioPortConfigSessionExt.aidl",
+        "aidl/android/media/AudioPortDeviceExt.aidl",
+        "aidl/android/media/AudioPortExt.aidl",
+        "aidl/android/media/AudioPortMixExt.aidl",
         "aidl/android/media/AudioPortRole.aidl",
+        "aidl/android/media/AudioPortSessionExt.aidl",
         "aidl/android/media/AudioPortType.aidl",
         "aidl/android/media/AudioProfile.aidl",
         "aidl/android/media/AudioSourceType.aidl",
@@ -309,7 +314,6 @@
         "aidl/android/media/AudioUniqueIdUse.aidl",
         "aidl/android/media/AudioUsage.aidl",
         "aidl/android/media/AudioUuid.aidl",
-        "aidl/android/media/DeviceDescriptorBase.aidl",
         "aidl/android/media/EffectDescriptor.aidl",
     ],
     imports: [
diff --git a/media/libaudioclient/IAudioFlinger.cpp b/media/libaudioclient/IAudioFlinger.cpp
index 5dfda09..786af53 100644
--- a/media/libaudioclient/IAudioFlinger.cpp
+++ b/media/libaudioclient/IAudioFlinger.cpp
@@ -24,77 +24,10 @@
 
 #include <binder/IPCThreadState.h>
 #include <binder/Parcel.h>
-#include <media/AudioValidator.h>
-#include <media/IAudioPolicyService.h>
-#include <mediautils/ServiceUtilities.h>
-#include <mediautils/TimeCheck.h>
 #include "IAudioFlinger.h"
 
 namespace android {
 
-enum {
-    CREATE_TRACK = IBinder::FIRST_CALL_TRANSACTION,
-    CREATE_RECORD,
-    SAMPLE_RATE,
-    RESERVED,   // obsolete, was CHANNEL_COUNT
-    FORMAT,
-    FRAME_COUNT,
-    LATENCY,
-    SET_MASTER_VOLUME,
-    SET_MASTER_MUTE,
-    MASTER_VOLUME,
-    MASTER_MUTE,
-    SET_STREAM_VOLUME,
-    SET_STREAM_MUTE,
-    STREAM_VOLUME,
-    STREAM_MUTE,
-    SET_MODE,
-    SET_MIC_MUTE,
-    GET_MIC_MUTE,
-    SET_RECORD_SILENCED,
-    SET_PARAMETERS,
-    GET_PARAMETERS,
-    REGISTER_CLIENT,
-    GET_INPUTBUFFERSIZE,
-    OPEN_OUTPUT,
-    OPEN_DUPLICATE_OUTPUT,
-    CLOSE_OUTPUT,
-    SUSPEND_OUTPUT,
-    RESTORE_OUTPUT,
-    OPEN_INPUT,
-    CLOSE_INPUT,
-    INVALIDATE_STREAM,
-    SET_VOICE_VOLUME,
-    GET_RENDER_POSITION,
-    GET_INPUT_FRAMES_LOST,
-    NEW_AUDIO_UNIQUE_ID,
-    ACQUIRE_AUDIO_SESSION_ID,
-    RELEASE_AUDIO_SESSION_ID,
-    QUERY_NUM_EFFECTS,
-    QUERY_EFFECT,
-    GET_EFFECT_DESCRIPTOR,
-    CREATE_EFFECT,
-    MOVE_EFFECTS,
-    LOAD_HW_MODULE,
-    GET_PRIMARY_OUTPUT_SAMPLING_RATE,
-    GET_PRIMARY_OUTPUT_FRAME_COUNT,
-    SET_LOW_RAM_DEVICE,
-    LIST_AUDIO_PORTS,
-    GET_AUDIO_PORT,
-    CREATE_AUDIO_PATCH,
-    RELEASE_AUDIO_PATCH,
-    LIST_AUDIO_PATCHES,
-    SET_AUDIO_PORT_CONFIG,
-    GET_AUDIO_HW_SYNC_FOR_SESSION,
-    SYSTEM_READY,
-    FRAME_COUNT_HAL,
-    GET_MICROPHONES,
-    SET_MASTER_BALANCE,
-    GET_MASTER_BALANCE,
-    SET_EFFECT_SUSPENDED,
-    SET_AUDIO_HAL_PIDS
-};
-
 #define MAX_ITEMS_PER_LIST 1024
 
 ConversionResult<media::CreateTrackRequest> IAudioFlinger::CreateTrackInput::toAidl() const {
@@ -988,106 +921,6 @@
 status_t BnAudioFlinger::onTransact(
     uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
 {
-    // make sure transactions reserved to AudioPolicyManager do not come from other processes
-    switch (code) {
-        case SET_STREAM_VOLUME:
-        case SET_STREAM_MUTE:
-        case OPEN_OUTPUT:
-        case OPEN_DUPLICATE_OUTPUT:
-        case CLOSE_OUTPUT:
-        case SUSPEND_OUTPUT:
-        case RESTORE_OUTPUT:
-        case OPEN_INPUT:
-        case CLOSE_INPUT:
-        case INVALIDATE_STREAM:
-        case SET_VOICE_VOLUME:
-        case MOVE_EFFECTS:
-        case SET_EFFECT_SUSPENDED:
-        case LOAD_HW_MODULE:
-        case LIST_AUDIO_PORTS:
-        case GET_AUDIO_PORT:
-        case CREATE_AUDIO_PATCH:
-        case RELEASE_AUDIO_PATCH:
-        case LIST_AUDIO_PATCHES:
-        case SET_AUDIO_PORT_CONFIG:
-        case SET_RECORD_SILENCED:
-            ALOGW("%s: transaction %d received from PID %d",
-                  __func__, code, IPCThreadState::self()->getCallingPid());
-            // return status only for non void methods
-            switch (code) {
-                case SET_RECORD_SILENCED:
-                case SET_EFFECT_SUSPENDED:
-                    break;
-                default:
-                    reply->writeInt32(static_cast<int32_t> (INVALID_OPERATION));
-                    break;
-            }
-            return OK;
-        default:
-            break;
-    }
-
-    // make sure the following transactions come from system components
-    switch (code) {
-        case SET_MASTER_VOLUME:
-        case SET_MASTER_MUTE:
-        case SET_MODE:
-        case SET_MIC_MUTE:
-        case SET_LOW_RAM_DEVICE:
-        case SYSTEM_READY:
-        case SET_AUDIO_HAL_PIDS: {
-            if (!isServiceUid(IPCThreadState::self()->getCallingUid())) {
-                ALOGW("%s: transaction %d received from PID %d unauthorized UID %d",
-                      __func__, code, IPCThreadState::self()->getCallingPid(),
-                      IPCThreadState::self()->getCallingUid());
-                // return status only for non void methods
-                switch (code) {
-                    case SYSTEM_READY:
-                        break;
-                    default:
-                        reply->writeInt32(static_cast<int32_t> (INVALID_OPERATION));
-                        break;
-                }
-                return OK;
-            }
-        } break;
-        default:
-            break;
-    }
-
-    // List of relevant events that trigger log merging.
-    // Log merging should activate during audio activity of any kind. This are considered the
-    // most relevant events.
-    // TODO should select more wisely the items from the list
-    switch (code) {
-        case CREATE_TRACK:
-        case CREATE_RECORD:
-        case SET_MASTER_VOLUME:
-        case SET_MASTER_MUTE:
-        case SET_MIC_MUTE:
-        case SET_PARAMETERS:
-        case CREATE_EFFECT:
-        case SYSTEM_READY: {
-            requestLogMerge();
-            break;
-        }
-        default:
-            break;
-    }
-
-    std::string tag("IAudioFlinger command " + std::to_string(code));
-    TimeCheck check(tag.c_str());
-
-    // Make sure we connect to Audio Policy Service before calling into AudioFlinger:
-    //  - AudioFlinger can call into Audio Policy Service with its global mutex held
-    //  - If this is the first time Audio Policy Service is queried from inside audioserver process
-    //  this will trigger Audio Policy Manager initialization.
-    //  - Audio Policy Manager initialization calls into AudioFlinger which will try to lock
-    //  its global mutex and a deadlock will occur.
-    if (IPCThreadState::self()->getCallingPid() != getpid()) {
-        AudioSystem::get_audio_policy_service();
-    }
-
     switch (code) {
         case CREATE_TRACK: {
             CHECK_INTERFACE(IAudioFlinger, data, reply);
@@ -1496,10 +1329,6 @@
                 ALOGE("b/23905951");
                 return status;
             }
-            status = AudioValidator::validateAudioPort(port);
-            if (status == NO_ERROR) {
-                status = getAudioPort(&port);
-            }
             reply->writeInt32(status);
             if (status == NO_ERROR) {
                 reply->write(&port, sizeof(struct audio_port_v7));
@@ -1519,10 +1348,6 @@
                 ALOGE("b/23905951");
                 return status;
             }
-            status = AudioValidator::validateAudioPatch(patch);
-            if (status == NO_ERROR) {
-                status = createAudioPatch(&patch, &handle);
-            }
             reply->writeInt32(status);
             if (status == NO_ERROR) {
                 reply->write(&handle, sizeof(audio_patch_handle_t));
@@ -1571,10 +1396,6 @@
             if (status != NO_ERROR) {
                 return status;
             }
-            status = AudioValidator::validateAudioPortConfig(config);
-            if (status == NO_ERROR) {
-                status = setAudioPortConfig(&config);
-            }
             reply->writeInt32(status);
             return NO_ERROR;
         } break;
diff --git a/media/libaudioclient/aidl/android/media/DeviceDescriptorBase.aidl b/media/libaudioclient/aidl/android/media/AudioMixLatencyClass.aidl
similarity index 62%
copy from media/libaudioclient/aidl/android/media/DeviceDescriptorBase.aidl
copy to media/libaudioclient/aidl/android/media/AudioMixLatencyClass.aidl
index aa0f149..d70b364 100644
--- a/media/libaudioclient/aidl/android/media/DeviceDescriptorBase.aidl
+++ b/media/libaudioclient/aidl/android/media/AudioMixLatencyClass.aidl
@@ -13,22 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package android.media;
 
-import android.media.AudioPort;
-import android.media.AudioPortConfig;
-import android.media.AudioDevice;
-
 /**
  * {@hide}
  */
-parcelable DeviceDescriptorBase {
-    AudioPort port;
-    AudioPortConfig portConfig;
-    AudioDevice device;
-    /** Bitmask, indexed by AudioEncapsulationMode. */
-    int encapsulationModes;
-    /** Bitmask, indexed by AudioEncapsulationMetadataType. */
-    int encapsulationMetadataTypes;
+@Backing(type="int")
+enum AudioMixLatencyClass {
+    LOW = 0,
+    NORMAL = 1,
 }
diff --git a/media/libaudioclient/aidl/android/media/AudioPort.aidl b/media/libaudioclient/aidl/android/media/AudioPort.aidl
index 1aa532b..123aeb0 100644
--- a/media/libaudioclient/aidl/android/media/AudioPort.aidl
+++ b/media/libaudioclient/aidl/android/media/AudioPort.aidl
@@ -17,6 +17,8 @@
 package android.media;
 
 import android.media.AudioGain;
+import android.media.AudioPortConfig;
+import android.media.AudioPortExt;
 import android.media.AudioPortRole;
 import android.media.AudioPortType;
 import android.media.AudioProfile;
@@ -25,11 +27,18 @@
  * {@hide}
  */
 parcelable AudioPort {
-    /** Gain controllers. */
-    AudioGain[] gains;
-    @utf8InCpp String name;
-    AudioPortType type;
+    /** Port unique ID. Interpreted as audio_port_handle_t. */
+    int id;
+    /** Sink or source. */
     AudioPortRole role;
+    /** Device, mix ... */
+    AudioPortType type;
+    @utf8InCpp String name;
     /** AudioProfiles supported by this port (format, Rates, Channels). */
     AudioProfile[] profiles;
+    /** Gain controllers. */
+    AudioGain[] gains;
+    /** Current audio port configuration. */
+    AudioPortConfig activeConfig;
+    AudioPortExt ext;
 }
diff --git a/media/libaudioclient/aidl/android/media/AudioPortConfigExt.aidl b/media/libaudioclient/aidl/android/media/AudioPortConfigExt.aidl
index 38da4f5..5d635b6 100644
--- a/media/libaudioclient/aidl/android/media/AudioPortConfigExt.aidl
+++ b/media/libaudioclient/aidl/android/media/AudioPortConfigExt.aidl
@@ -29,7 +29,7 @@
      * TODO(ytai): replace with the canonical representation for an empty union, as soon as it is
      *             established.
      */
-    boolean nothing;
+    boolean unspecified;
     /** Device specific info. */
     AudioPortConfigDeviceExt device;
     /** Mix specific info. */
diff --git a/media/libaudioclient/aidl/android/media/AudioPortConfigMixExtUseCase.aidl b/media/libaudioclient/aidl/android/media/AudioPortConfigMixExtUseCase.aidl
index 9e5e081..c61f044 100644
--- a/media/libaudioclient/aidl/android/media/AudioPortConfigMixExtUseCase.aidl
+++ b/media/libaudioclient/aidl/android/media/AudioPortConfigMixExtUseCase.aidl
@@ -29,7 +29,7 @@
      * TODO(ytai): replace with the canonical representation for an empty union, as soon as it is
      *             established.
      */
-    boolean nothing;
+    boolean unspecified;
     /** This to be set if the containing config has the AudioPortRole::SOURCE role. */
     AudioStreamType stream;
     /** This to be set if the containing config has the AudioPortRole::SINK role. */
diff --git a/media/libaudioclient/aidl/android/media/DeviceDescriptorBase.aidl b/media/libaudioclient/aidl/android/media/AudioPortDeviceExt.aidl
similarity index 85%
rename from media/libaudioclient/aidl/android/media/DeviceDescriptorBase.aidl
rename to media/libaudioclient/aidl/android/media/AudioPortDeviceExt.aidl
index aa0f149..b758f23 100644
--- a/media/libaudioclient/aidl/android/media/DeviceDescriptorBase.aidl
+++ b/media/libaudioclient/aidl/android/media/AudioPortDeviceExt.aidl
@@ -16,16 +16,14 @@
 
 package android.media;
 
-import android.media.AudioPort;
-import android.media.AudioPortConfig;
 import android.media.AudioDevice;
 
 /**
  * {@hide}
  */
-parcelable DeviceDescriptorBase {
-    AudioPort port;
-    AudioPortConfig portConfig;
+parcelable AudioPortDeviceExt {
+    /** Module the device is attached to. Interpreted as audio_module_handle_t. */
+    int hwModule;
     AudioDevice device;
     /** Bitmask, indexed by AudioEncapsulationMode. */
     int encapsulationModes;
diff --git a/media/libaudioclient/aidl/android/media/AudioPortExt.aidl b/media/libaudioclient/aidl/android/media/AudioPortExt.aidl
new file mode 100644
index 0000000..453784b
--- /dev/null
+++ b/media/libaudioclient/aidl/android/media/AudioPortExt.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package android.media;
+
+import android.media.AudioPortDeviceExt;
+import android.media.AudioPortMixExt;
+import android.media.AudioPortSessionExt;
+
+/**
+ * {@hide}
+ */
+union AudioPortExt {
+    /**
+     * This represents an empty union. Value is ignored.
+     * TODO(ytai): replace with the canonical representation for an empty union, as soon as it is
+     *             established.
+     */
+    boolean unspecified;
+    /** Device specific info. */
+    AudioPortDeviceExt device;
+    /** Mix specific info. */
+    AudioPortMixExt mix;
+    /** Session specific info. */
+    AudioPortSessionExt session;
+}
diff --git a/media/libaudioclient/aidl/android/media/DeviceDescriptorBase.aidl b/media/libaudioclient/aidl/android/media/AudioPortMixExt.aidl
similarity index 62%
copy from media/libaudioclient/aidl/android/media/DeviceDescriptorBase.aidl
copy to media/libaudioclient/aidl/android/media/AudioPortMixExt.aidl
index aa0f149..62cdb8e 100644
--- a/media/libaudioclient/aidl/android/media/DeviceDescriptorBase.aidl
+++ b/media/libaudioclient/aidl/android/media/AudioPortMixExt.aidl
@@ -16,19 +16,16 @@
 
 package android.media;
 
-import android.media.AudioPort;
-import android.media.AudioPortConfig;
-import android.media.AudioDevice;
+import android.media.AudioMixLatencyClass;
 
 /**
  * {@hide}
  */
-parcelable DeviceDescriptorBase {
-    AudioPort port;
-    AudioPortConfig portConfig;
-    AudioDevice device;
-    /** Bitmask, indexed by AudioEncapsulationMode. */
-    int encapsulationModes;
-    /** Bitmask, indexed by AudioEncapsulationMetadataType. */
-    int encapsulationMetadataTypes;
+parcelable AudioPortMixExt {
+    /** Module the stream is attached to. Interpreted as audio_module_handle_t. */
+    int hwModule;
+    /** I/O handle of the input/output stream. Interpreted as audio_io_handle_t. */
+    int handle;
+    /** Latency class */
+    AudioMixLatencyClass latencyClass;
 }
diff --git a/media/libaudioclient/aidl/android/media/DeviceDescriptorBase.aidl b/media/libaudioclient/aidl/android/media/AudioPortSessionExt.aidl
similarity index 62%
copy from media/libaudioclient/aidl/android/media/DeviceDescriptorBase.aidl
copy to media/libaudioclient/aidl/android/media/AudioPortSessionExt.aidl
index aa0f149..dbca168 100644
--- a/media/libaudioclient/aidl/android/media/DeviceDescriptorBase.aidl
+++ b/media/libaudioclient/aidl/android/media/AudioPortSessionExt.aidl
@@ -16,19 +16,10 @@
 
 package android.media;
 
-import android.media.AudioPort;
-import android.media.AudioPortConfig;
-import android.media.AudioDevice;
-
 /**
  * {@hide}
  */
-parcelable DeviceDescriptorBase {
-    AudioPort port;
-    AudioPortConfig portConfig;
-    AudioDevice device;
-    /** Bitmask, indexed by AudioEncapsulationMode. */
-    int encapsulationModes;
-    /** Bitmask, indexed by AudioEncapsulationMetadataType. */
-    int encapsulationMetadataTypes;
+parcelable AudioPortSessionExt {
+    /** Audio session. Interpreted as audio_session_t. */
+    int session;
 }
diff --git a/media/libaudioclient/aidl/android/media/OpenOutputRequest.aidl b/media/libaudioclient/aidl/android/media/OpenOutputRequest.aidl
index 4518adb..06b12e9 100644
--- a/media/libaudioclient/aidl/android/media/OpenOutputRequest.aidl
+++ b/media/libaudioclient/aidl/android/media/OpenOutputRequest.aidl
@@ -17,7 +17,7 @@
 package android.media;
 
 import android.media.AudioConfig;
-import android.media.DeviceDescriptorBase;
+import android.media.AudioPort;
 
 /**
  * {@hide}
@@ -26,7 +26,8 @@
     /** Interpreted as audio_module_handle_t. */
     int module;
     AudioConfig config;
-    DeviceDescriptorBase device;
+    /** Type must be DEVICE. */
+    AudioPort device;
     /** Bitmask, indexed by AudioOutputFlag. */
     int flags;
 }
diff --git a/media/libaudioclient/include/media/AidlConversion.h b/media/libaudioclient/include/media/AidlConversion.h
index 894e56e..2dc471b 100644
--- a/media/libaudioclient/include/media/AidlConversion.h
+++ b/media/libaudioclient/include/media/AidlConversion.h
@@ -28,12 +28,20 @@
 #include <android/media/AudioEncapsulationMode.h>
 #include <android/media/AudioEncapsulationMetadataType.h>
 #include <android/media/AudioFlag.h>
+#include <android/media/AudioGain.h>
 #include <android/media/AudioGainMode.h>
 #include <android/media/AudioInputFlags.h>
 #include <android/media/AudioIoConfigEvent.h>
 #include <android/media/AudioIoDescriptor.h>
+#include <android/media/AudioMixLatencyClass.h>
 #include <android/media/AudioOutputFlags.h>
+#include <android/media/AudioPort.h>
 #include <android/media/AudioPortConfigType.h>
+#include <android/media/AudioPortDeviceExt.h>
+#include <android/media/AudioPortExt.h>
+#include <android/media/AudioPortMixExt.h>
+#include <android/media/AudioPortSessionExt.h>
+#include <android/media/AudioProfile.h>
 #include <android/media/AudioTimestampInternal.h>
 #include <android/media/EffectDescriptor.h>
 
@@ -110,11 +118,13 @@
 ConversionResult<media::audio::common::AudioFormat> legacy2aidl_audio_format_t_AudioFormat(
         audio_format_t legacy);
 
-ConversionResult<int> aidl2legacy_AudioGainMode_int(media::AudioGainMode aidl);
-ConversionResult<media::AudioGainMode> legacy2aidl_int_AudioGainMode(int legacy);
+ConversionResult<audio_gain_mode_t>
+aidl2legacy_AudioGainMode_audio_gain_mode_t(media::AudioGainMode aidl);
+ConversionResult<media::AudioGainMode>
+legacy2aidl_audio_gain_mode_t_AudioGainMode(audio_gain_mode_t legacy);
 
-ConversionResult<audio_gain_mode_t> aidl2legacy_int32_t_audio_gain_mode_t(int32_t aidl);
-ConversionResult<int32_t> legacy2aidl_audio_gain_mode_t_int32_t(audio_gain_mode_t legacy);
+ConversionResult<audio_gain_mode_t> aidl2legacy_int32_t_audio_gain_mode_t_mask(int32_t aidl);
+ConversionResult<int32_t> legacy2aidl_audio_gain_mode_t_mask_int32_t(audio_gain_mode_t legacy);
 
 ConversionResult<audio_devices_t> aidl2legacy_int32_t_audio_devices_t(int32_t aidl);
 ConversionResult<int32_t> legacy2aidl_audio_devices_t_int32_t(audio_devices_t legacy);
@@ -279,4 +289,41 @@
 ConversionResult<int32_t>
 legacy2aidl_AudioEncapsulationMetadataType_mask(uint32_t legacy);
 
+ConversionResult<audio_mix_latency_class_t>
+aidl2legacy_AudioMixLatencyClass_audio_mix_latency_class_t(
+        media::AudioMixLatencyClass aidl);
+ConversionResult<media::AudioMixLatencyClass>
+legacy2aidl_audio_mix_latency_class_t_AudioMixLatencyClass(
+        audio_mix_latency_class_t legacy);
+
+ConversionResult<audio_port_device_ext>
+aidl2legacy_AudioPortDeviceExt_audio_port_device_ext(const media::AudioPortDeviceExt& aidl);
+ConversionResult<media::AudioPortDeviceExt>
+legacy2aidl_audio_port_device_ext_AudioPortDeviceExt(const audio_port_device_ext& legacy);
+
+ConversionResult<audio_port_mix_ext>
+aidl2legacy_AudioPortMixExt_audio_port_mix_ext(const media::AudioPortMixExt& aidl);
+ConversionResult<media::AudioPortMixExt>
+legacy2aidl_audio_port_mix_ext_AudioPortMixExt(const audio_port_mix_ext& legacy);
+
+ConversionResult<audio_port_session_ext>
+aidl2legacy_AudioPortSessionExt_audio_port_session_ext(const media::AudioPortSessionExt& aidl);
+ConversionResult<media::AudioPortSessionExt>
+legacy2aidl_audio_port_session_ext_AudioPortSessionExt(const audio_port_session_ext& legacy);
+
+ConversionResult<audio_profile>
+aidl2legacy_AudioProfile_audio_profile(const media::AudioProfile& aidl);
+ConversionResult<media::AudioProfile>
+legacy2aidl_audio_profile_AudioProfile(const audio_profile& legacy);
+
+ConversionResult<audio_gain>
+aidl2legacy_AudioGain_audio_gain(const media::AudioGain& aidl);
+ConversionResult<media::AudioGain>
+legacy2aidl_audio_gain_AudioGain(const audio_gain& legacy);
+
+ConversionResult<audio_port_v7>
+aidl2legacy_AudioPort_audio_port_v7(const media::AudioPort& aidl);
+ConversionResult<media::AudioPort>
+legacy2aidl_audio_port_v7_AudioPort(const audio_port_v7& legacy);
+
 }  // namespace android
diff --git a/media/libaudioclient/include/media/AidlConversionUtil.h b/media/libaudioclient/include/media/AidlConversionUtil.h
index 00e5ff2..6bfb743 100644
--- a/media/libaudioclient/include/media/AidlConversionUtil.h
+++ b/media/libaudioclient/include/media/AidlConversionUtil.h
@@ -82,6 +82,20 @@
 }
 
 /**
+ * A generic template that helps convert containers of convertible types, using iterators.
+ */
+template<typename InputIterator, typename OutputIterator, typename Func>
+status_t convertRange(InputIterator start,
+                      InputIterator end,
+                      OutputIterator out,
+                      const Func& itemConversion) {
+    for (InputIterator iter = start; iter != end; ++iter, ++out) {
+        *out = VALUE_OR_RETURN_STATUS(itemConversion(*iter));
+    }
+    return OK;
+}
+
+/**
  * A generic template that helps convert containers of convertible types.
  */
 template<typename OutputContainer, typename InputContainer, typename Func>
@@ -95,4 +109,27 @@
     return output;
 }
 
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Utilities for working with AIDL unions.
+// UNION_GET(obj, fieldname) returns a ConversionResult<T> containing either the strongly-typed
+//   value of the respective field, or BAD_VALUE if the union is not set to the requested field.
+// UNION_SET(obj, fieldname, value) sets the requested field to the given value.
+
+template<typename T, typename T::Tag tag>
+using UnionFieldType = std::decay_t<decltype(std::declval<T>().template get<tag>())>;
+
+template<typename T, typename T::Tag tag>
+ConversionResult<UnionFieldType<T, tag>> unionGetField(const T& u) {
+    if (u.getTag() != tag) {
+        return base::unexpected(BAD_VALUE);
+    }
+    return u.template get<tag>();
+}
+
+#define UNION_GET(u, field) \
+    unionGetField<std::decay_t<decltype(u)>, std::decay_t<decltype(u)>::Tag::field>(u)
+
+#define UNION_SET(u, field, value) \
+    (u).set<std::decay_t<decltype(u)>::Tag::field>(value)
+
 }  // namespace android
diff --git a/media/libaudioclient/include/media/IAudioFlinger.h b/media/libaudioclient/include/media/IAudioFlinger.h
index 11d341e..911a34f 100644
--- a/media/libaudioclient/include/media/IAudioFlinger.h
+++ b/media/libaudioclient/include/media/IAudioFlinger.h
@@ -333,6 +333,70 @@
     virtual status_t getMicrophones(std::vector<media::MicrophoneInfo> *microphones) = 0;
 
     virtual status_t setAudioHalPids(const std::vector<pid_t>& pids) = 0;
+
+protected:
+    enum {
+        CREATE_TRACK = IBinder::FIRST_CALL_TRANSACTION,
+        CREATE_RECORD,
+        SAMPLE_RATE,
+        RESERVED,   // obsolete, was CHANNEL_COUNT
+        FORMAT,
+        FRAME_COUNT,
+        LATENCY,
+        SET_MASTER_VOLUME,
+        SET_MASTER_MUTE,
+        MASTER_VOLUME,
+        MASTER_MUTE,
+        SET_STREAM_VOLUME,
+        SET_STREAM_MUTE,
+        STREAM_VOLUME,
+        STREAM_MUTE,
+        SET_MODE,
+        SET_MIC_MUTE,
+        GET_MIC_MUTE,
+        SET_RECORD_SILENCED,
+        SET_PARAMETERS,
+        GET_PARAMETERS,
+        REGISTER_CLIENT,
+        GET_INPUTBUFFERSIZE,
+        OPEN_OUTPUT,
+        OPEN_DUPLICATE_OUTPUT,
+        CLOSE_OUTPUT,
+        SUSPEND_OUTPUT,
+        RESTORE_OUTPUT,
+        OPEN_INPUT,
+        CLOSE_INPUT,
+        INVALIDATE_STREAM,
+        SET_VOICE_VOLUME,
+        GET_RENDER_POSITION,
+        GET_INPUT_FRAMES_LOST,
+        NEW_AUDIO_UNIQUE_ID,
+        ACQUIRE_AUDIO_SESSION_ID,
+        RELEASE_AUDIO_SESSION_ID,
+        QUERY_NUM_EFFECTS,
+        QUERY_EFFECT,
+        GET_EFFECT_DESCRIPTOR,
+        CREATE_EFFECT,
+        MOVE_EFFECTS,
+        LOAD_HW_MODULE,
+        GET_PRIMARY_OUTPUT_SAMPLING_RATE,
+        GET_PRIMARY_OUTPUT_FRAME_COUNT,
+        SET_LOW_RAM_DEVICE,
+        LIST_AUDIO_PORTS,
+        GET_AUDIO_PORT,
+        CREATE_AUDIO_PATCH,
+        RELEASE_AUDIO_PATCH,
+        LIST_AUDIO_PATCHES,
+        SET_AUDIO_PORT_CONFIG,
+        GET_AUDIO_HW_SYNC_FOR_SESSION,
+        SYSTEM_READY,
+        FRAME_COUNT_HAL,
+        GET_MICROPHONES,
+        SET_MASTER_BALANCE,
+        GET_MASTER_BALANCE,
+        SET_EFFECT_SUSPENDED,
+        SET_AUDIO_HAL_PIDS
+    };
 };
 
 
@@ -345,9 +409,6 @@
                                     const Parcel& data,
                                     Parcel* reply,
                                     uint32_t flags = 0);
-
-    // Requests media.log to start merging log buffers
-    virtual void requestLogMerge() = 0;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/media/libaudiofoundation/AudioGain.cpp b/media/libaudiofoundation/AudioGain.cpp
index c59e966..56343d8 100644
--- a/media/libaudiofoundation/AudioGain.cpp
+++ b/media/libaudiofoundation/AudioGain.cpp
@@ -139,7 +139,8 @@
     parcelable->index = VALUE_OR_RETURN_STATUS(convertIntegral<int32_t>(mIndex));
     parcelable->useInChannelMask = mUseInChannelMask;
     parcelable->useForVolume = mUseForVolume;
-    parcelable->mode = VALUE_OR_RETURN_STATUS(legacy2aidl_audio_gain_mode_t_int32_t(mGain.mode));
+    parcelable->mode = VALUE_OR_RETURN_STATUS(
+            legacy2aidl_audio_gain_mode_t_mask_int32_t(mGain.mode));
     parcelable->channelMask = VALUE_OR_RETURN_STATUS(
             legacy2aidl_audio_channel_mask_t_int32_t(mGain.channel_mask));
     parcelable->minValue = VALUE_OR_RETURN_STATUS(convertIntegral<int32_t>(mGain.min_value));
@@ -162,7 +163,8 @@
     mIndex = VALUE_OR_RETURN_STATUS(convertIntegral<int>(parcelable.index));
     mUseInChannelMask = parcelable.useInChannelMask;
     mUseForVolume = parcelable.useForVolume;
-    mGain.mode = VALUE_OR_RETURN_STATUS(aidl2legacy_int32_t_audio_gain_mode_t(parcelable.mode));
+    mGain.mode = VALUE_OR_RETURN_STATUS(
+            aidl2legacy_int32_t_audio_gain_mode_t_mask(parcelable.mode));
     mGain.channel_mask = VALUE_OR_RETURN_STATUS(
             aidl2legacy_int32_t_audio_channel_mask_t(parcelable.channelMask));
     mGain.min_value = VALUE_OR_RETURN_STATUS(convertIntegral<int>(parcelable.minValue));
diff --git a/media/libaudiofoundation/AudioPort.cpp b/media/libaudiofoundation/AudioPort.cpp
index 559c711..6b63675 100644
--- a/media/libaudiofoundation/AudioPort.cpp
+++ b/media/libaudiofoundation/AudioPort.cpp
@@ -291,7 +291,7 @@
     parcelable->id = VALUE_OR_RETURN_STATUS(legacy2aidl_audio_port_handle_t_int32_t(mId));
     parcelable->gain.index = VALUE_OR_RETURN_STATUS(convertIntegral<int32_t>(mGain.index));
     parcelable->gain.mode = VALUE_OR_RETURN_STATUS(
-            legacy2aidl_audio_gain_mode_t_int32_t(mGain.mode));
+            legacy2aidl_audio_gain_mode_t_mask_int32_t(mGain.mode));
     parcelable->gain.channelMask = VALUE_OR_RETURN_STATUS(
             legacy2aidl_audio_channel_mask_t_int32_t(mGain.channel_mask));
     parcelable->gain.rampDurationMs = VALUE_OR_RETURN_STATUS(
@@ -315,7 +315,7 @@
     mId = VALUE_OR_RETURN_STATUS(aidl2legacy_int32_t_audio_port_handle_t(parcelable.id));
     mGain.index = VALUE_OR_RETURN_STATUS(convertIntegral<int>(parcelable.gain.index));
     mGain.mode = VALUE_OR_RETURN_STATUS(
-            aidl2legacy_int32_t_audio_gain_mode_t(parcelable.gain.mode));
+            aidl2legacy_int32_t_audio_gain_mode_t_mask(parcelable.gain.mode));
     mGain.channel_mask = VALUE_OR_RETURN_STATUS(
             aidl2legacy_int32_t_audio_channel_mask_t(parcelable.gain.channelMask));
     mGain.ramp_duration_ms = VALUE_OR_RETURN_STATUS(
diff --git a/media/libaudiofoundation/DeviceDescriptorBase.cpp b/media/libaudiofoundation/DeviceDescriptorBase.cpp
index 6261559..a3e9589 100644
--- a/media/libaudiofoundation/DeviceDescriptorBase.cpp
+++ b/media/libaudiofoundation/DeviceDescriptorBase.cpp
@@ -159,41 +159,49 @@
 
 status_t DeviceDescriptorBase::writeToParcel(Parcel *parcel) const
 {
-    media::DeviceDescriptorBase parcelable;
+    media::AudioPort parcelable;
     return writeToParcelable(&parcelable)
         ?: parcelable.writeToParcel(parcel);
 }
 
-status_t DeviceDescriptorBase::writeToParcelable(media::DeviceDescriptorBase* parcelable) const {
-    AudioPort::writeToParcelable(&parcelable->port);
-    AudioPortConfig::writeToParcelable(&parcelable->portConfig);
-    parcelable->device = VALUE_OR_RETURN_STATUS(
-            legacy2aidl_AudioDeviceTypeAddress(mDeviceTypeAddr));
-    parcelable->encapsulationModes = VALUE_OR_RETURN_STATUS(
+status_t DeviceDescriptorBase::writeToParcelable(media::AudioPort* parcelable) const {
+    AudioPort::writeToParcelable(parcelable);
+    AudioPortConfig::writeToParcelable(&parcelable->activeConfig);
+    parcelable->id = VALUE_OR_RETURN_STATUS(legacy2aidl_audio_port_handle_t_int32_t(mId));
+
+    media::AudioPortDeviceExt ext;
+    ext.device = VALUE_OR_RETURN_STATUS(legacy2aidl_AudioDeviceTypeAddress(mDeviceTypeAddr));
+    ext.encapsulationModes = VALUE_OR_RETURN_STATUS(
             legacy2aidl_AudioEncapsulationMode_mask(mEncapsulationModes));
-    parcelable->encapsulationMetadataTypes = VALUE_OR_RETURN_STATUS(
+    ext.encapsulationMetadataTypes = VALUE_OR_RETURN_STATUS(
             legacy2aidl_AudioEncapsulationMetadataType_mask(mEncapsulationMetadataTypes));
+    UNION_SET(parcelable->ext, device, std::move(ext));
     return OK;
 }
 
 status_t DeviceDescriptorBase::readFromParcel(const Parcel *parcel) {
-    media::DeviceDescriptorBase parcelable;
+    media::AudioPort parcelable;
     return parcelable.readFromParcel(parcel)
         ?: readFromParcelable(parcelable);
 }
 
-status_t DeviceDescriptorBase::readFromParcelable(const media::DeviceDescriptorBase& parcelable) {
-    status_t status = AudioPort::readFromParcelable(parcelable.port)
-                      ?: AudioPortConfig::readFromParcelable(parcelable.portConfig);
+status_t DeviceDescriptorBase::readFromParcelable(const media::AudioPort& parcelable) {
+    if (parcelable.type != media::AudioPortType::DEVICE) {
+        return BAD_VALUE;
+    }
+    status_t status = AudioPort::readFromParcelable(parcelable)
+                      ?: AudioPortConfig::readFromParcelable(parcelable.activeConfig);
     if (status != OK) {
         return status;
     }
+
+    media::AudioPortDeviceExt ext = VALUE_OR_RETURN_STATUS(UNION_GET(parcelable.ext, device));
     mDeviceTypeAddr = VALUE_OR_RETURN_STATUS(
-            aidl2legacy_AudioDeviceTypeAddress(parcelable.device));
+            aidl2legacy_AudioDeviceTypeAddress(ext.device));
     mEncapsulationModes = VALUE_OR_RETURN_STATUS(
-            aidl2legacy_AudioEncapsulationMode_mask(parcelable.encapsulationModes));
+            aidl2legacy_AudioEncapsulationMode_mask(ext.encapsulationModes));
     mEncapsulationMetadataTypes = VALUE_OR_RETURN_STATUS(
-            aidl2legacy_AudioEncapsulationMetadataType_mask(parcelable.encapsulationMetadataTypes));
+            aidl2legacy_AudioEncapsulationMetadataType_mask(ext.encapsulationMetadataTypes));
     return OK;
 }
 
@@ -219,7 +227,7 @@
 }
 
 ConversionResult<sp<DeviceDescriptorBase>>
-aidl2legacy_DeviceDescriptorBase(const media::DeviceDescriptorBase& aidl) {
+aidl2legacy_DeviceDescriptorBase(const media::AudioPort& aidl) {
     sp<DeviceDescriptorBase> result = new DeviceDescriptorBase(AUDIO_DEVICE_NONE);
     status_t status = result->readFromParcelable(aidl);
     if (status != OK) {
@@ -228,9 +236,9 @@
     return result;
 }
 
-ConversionResult<media::DeviceDescriptorBase>
+ConversionResult<media::AudioPort>
 legacy2aidl_DeviceDescriptorBase(const sp<DeviceDescriptorBase>& legacy) {
-    media::DeviceDescriptorBase aidl;
+    media::AudioPort aidl;
     status_t status = legacy->writeToParcelable(&aidl);
     if (status != OK) {
         return base::unexpected(status);
diff --git a/media/libaudiofoundation/include/media/DeviceDescriptorBase.h b/media/libaudiofoundation/include/media/DeviceDescriptorBase.h
index 8a920b7..140ce36 100644
--- a/media/libaudiofoundation/include/media/DeviceDescriptorBase.h
+++ b/media/libaudiofoundation/include/media/DeviceDescriptorBase.h
@@ -18,7 +18,7 @@
 
 #include <vector>
 
-#include <android/media/DeviceDescriptorBase.h>
+#include <android/media/AudioPort.h>
 #include <binder/Parcel.h>
 #include <binder/Parcelable.h>
 #include <media/AudioContainers.h>
@@ -77,8 +77,8 @@
     status_t writeToParcel(Parcel* parcel) const override;
     status_t readFromParcel(const Parcel* parcel) override;
 
-    status_t writeToParcelable(media::DeviceDescriptorBase* parcelable) const;
-    status_t readFromParcelable(const media::DeviceDescriptorBase& parcelable);
+    status_t writeToParcelable(media::AudioPort* parcelable) const;
+    status_t readFromParcelable(const media::AudioPort& parcelable);
 
 protected:
     AudioDeviceTypeAddr mDeviceTypeAddr;
@@ -113,8 +113,8 @@
 
 // Conversion routines, according to AidlConversion.h conventions.
 ConversionResult<sp<DeviceDescriptorBase>>
-aidl2legacy_DeviceDescriptorBase(const media::DeviceDescriptorBase& aidl);
-ConversionResult<media::DeviceDescriptorBase>
+aidl2legacy_DeviceDescriptorBase(const media::AudioPort& aidl);
+ConversionResult<media::AudioPort>
 legacy2aidl_DeviceDescriptorBase(const sp<DeviceDescriptorBase>& legacy);
 
 } // namespace android
diff --git a/media/libmediatranscoding/TEST_MAPPING b/media/libmediatranscoding/TEST_MAPPING
new file mode 100644
index 0000000..f8a9db9
--- /dev/null
+++ b/media/libmediatranscoding/TEST_MAPPING
@@ -0,0 +1,32 @@
+{
+    "presubmit": [
+        {
+            "name": "MediaSampleQueueTests"
+        },
+        {
+            "name": "MediaSampleReaderNDKTests"
+        },
+        {
+            "name": "MediaSampleWriterTests"
+        },
+        {
+            "name": "MediaTrackTranscoderTests"
+        },
+        {
+            "name": "MediaTranscoderTests"
+        },
+        {
+            "name": "PassthroughTrackTranscoderTests"
+        },
+        {
+            "name": "TranscodingClientManager_tests"
+        },
+        {
+            "name": "TranscodingSessionController_tests"
+        },
+        {
+            "name": "VideoTrackTranscoderTests"
+        }
+    ]
+}
+
diff --git a/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp b/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
index 53d567e..92ba818 100644
--- a/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
+++ b/media/libmediatranscoding/transcoder/MediaSampleReaderNDK.cpp
@@ -99,6 +99,7 @@
     }
 
     if (!AMediaExtractor_advance(mExtractor)) {
+        LOG(DEBUG) << "  EOS in advanceExtractor_l";
         mEosReached = true;
         for (auto it = mTrackSignals.begin(); it != mTrackSignals.end(); ++it) {
             it->second.notify_all();
@@ -137,6 +138,8 @@
         LOG(ERROR) << "Unable to seek to " << seekToTimeUs << ", target " << targetTimeUs;
         return status;
     }
+
+    mEosReached = false;
     mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
     int64_t sampleTimeUs = AMediaExtractor_getSampleTime(mExtractor);
 
@@ -181,6 +184,11 @@
     if (mEosReached) {
         return AMEDIA_ERROR_END_OF_STREAM;
     }
+
+    if (!mEnforceSequentialAccess) {
+        return moveToTrack_l(trackIndex);
+    }
+
     return AMEDIA_OK;
 }
 
@@ -228,6 +236,8 @@
 }
 
 media_status_t MediaSampleReaderNDK::setEnforceSequentialAccess(bool enforce) {
+    LOG(DEBUG) << "setEnforceSequentialAccess( " << enforce << " )";
+
     std::scoped_lock lock(mExtractorMutex);
 
     if (mEnforceSequentialAccess && !enforce) {
@@ -369,7 +379,11 @@
         info->presentationTimeUs = 0;
         info->flags = SAMPLE_FLAG_END_OF_STREAM;
         info->size = 0;
+        LOG(DEBUG) << "  getSampleInfoForTrack #" << trackIndex << ": End Of Stream";
+    } else {
+        LOG(ERROR) << "  getSampleInfoForTrack #" << trackIndex << ": Error " << status;
     }
+
     return status;
 }
 
diff --git a/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp b/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp
index afa5021..389b941 100644
--- a/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp
+++ b/media/libmediatranscoding/transcoder/MediaSampleWriter.cpp
@@ -79,7 +79,7 @@
 
 MediaSampleWriter::~MediaSampleWriter() {
     if (mState == STARTED) {
-        stop();  // Join thread.
+        stop();
     }
 }
 
@@ -169,38 +169,41 @@
     }
 
     mState = STARTED;
-    mThread = std::thread([this] {
-        media_status_t status = writeSamples();
+    std::thread([this] {
+        bool wasStopped = false;
+        media_status_t status = writeSamples(&wasStopped);
         if (auto callbacks = mCallbacks.lock()) {
-            callbacks->onFinished(this, status);
+            if (wasStopped && status == AMEDIA_OK) {
+                callbacks->onStopped(this);
+            } else {
+                callbacks->onFinished(this, status);
+            }
         }
-    });
+    }).detach();
     return true;
 }
 
-bool MediaSampleWriter::stop() {
+void MediaSampleWriter::stop() {
     {
         std::scoped_lock lock(mMutex);
         if (mState != STARTED) {
             LOG(ERROR) << "Sample writer is not started.";
-            return false;
+            return;
         }
         mState = STOPPED;
     }
 
     mSampleSignal.notify_all();
-    mThread.join();
-    return true;
 }
 
-media_status_t MediaSampleWriter::writeSamples() {
+media_status_t MediaSampleWriter::writeSamples(bool* wasStopped) {
     media_status_t muxerStatus = mMuxer->start();
     if (muxerStatus != AMEDIA_OK) {
         LOG(ERROR) << "Error starting muxer: " << muxerStatus;
         return muxerStatus;
     }
 
-    media_status_t writeStatus = runWriterLoop();
+    media_status_t writeStatus = runWriterLoop(wasStopped);
     if (writeStatus != AMEDIA_OK) {
         LOG(ERROR) << "Error writing samples: " << writeStatus;
     }
@@ -213,7 +216,7 @@
     return writeStatus != AMEDIA_OK ? writeStatus : muxerStatus;
 }
 
-media_status_t MediaSampleWriter::runWriterLoop() NO_THREAD_SAFETY_ANALYSIS {
+media_status_t MediaSampleWriter::runWriterLoop(bool* wasStopped) NO_THREAD_SAFETY_ANALYSIS {
     AMediaCodecBufferInfo bufferInfo;
     int32_t lastProgressUpdate = 0;
     int trackEosCount = 0;
@@ -242,8 +245,9 @@
                 mSampleSignal.wait(lock);
             }
 
-            if (mState != STARTED) {
-                return AMEDIA_ERROR_UNKNOWN;  // TODO(lnilsson): Custom error code.
+            if (mState == STOPPED) {
+                *wasStopped = true;
+                return AMEDIA_OK;
             }
 
             auto& topEntry = mSampleQueue.top();
diff --git a/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
index 698594f..15f7427 100644
--- a/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/MediaTrackTranscoder.cpp
@@ -69,41 +69,44 @@
         LOG(ERROR) << "TrackTranscoder must be configured before started";
         return false;
     }
+    mState = STARTED;
 
-    mTranscodingThread = std::thread([this] {
-        media_status_t status = runTranscodeLoop();
+    std::thread([this] {
+        bool stopped = false;
+        media_status_t status = runTranscodeLoop(&stopped);
+
+        // Output an EOS sample if the transcoder was stopped.
+        if (stopped) {
+            auto sample = std::make_shared<MediaSample>();
+            sample->info.flags = SAMPLE_FLAG_END_OF_STREAM;
+            onOutputSampleAvailable(sample);
+        }
 
         // Notify the client.
         if (auto callbacks = mTranscoderCallback.lock()) {
-            if (status != AMEDIA_OK) {
-                callbacks->onTrackError(this, status);
-            } else {
+            if (stopped) {
+                callbacks->onTrackStopped(this);
+            } else if (status == AMEDIA_OK) {
                 callbacks->onTrackFinished(this);
+            } else {
+                callbacks->onTrackError(this, status);
             }
         }
-    });
+    }).detach();
 
-    mState = STARTED;
     return true;
 }
 
-bool MediaTrackTranscoder::stop() {
+void MediaTrackTranscoder::stop(bool stopOnSyncSample) {
     std::scoped_lock lock{mStateMutex};
 
-    if (mState == STARTED) {
+    if (mState == STARTED || (mStopRequest == STOP_ON_SYNC && !stopOnSyncSample)) {
+        mStopRequest = stopOnSyncSample ? STOP_ON_SYNC : STOP_NOW;
         abortTranscodeLoop();
-        mMediaSampleReader->setEnforceSequentialAccess(false);
-        mTranscodingThread.join();
-        {
-            std::scoped_lock lock{mSampleMutex};
-            mSampleQueue.abort();  // Release any buffered samples.
-        }
         mState = STOPPED;
-        return true;
+    } else {
+        LOG(WARNING) << "TrackTranscoder must be started before stopped";
     }
-
-    LOG(ERROR) << "TrackTranscoder must be started before stopped";
-    return false;
 }
 
 void MediaTrackTranscoder::notifyTrackFormatAvailable() {
diff --git a/media/libmediatranscoding/transcoder/MediaTranscoder.cpp b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
index 07df5e0..94a9a33 100644
--- a/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/MediaTranscoder.cpp
@@ -69,38 +69,67 @@
     return format;
 }
 
-void MediaTranscoder::sendCallback(media_status_t status) {
-    // If the transcoder is already cancelled explicitly, don't send any error callbacks.
-    // Tracks and sample writer will report errors for abort. However, currently we can't
-    // tell it apart from real errors. Ideally we still want to report real errors back
-    // to client, as there is a small chance that explicit abort and the real error come
-    // at around the same time, we should report that if abort has a specific error code.
-    // On the other hand, if the transcoder actually finished (status is AMEDIA_OK) at around
-    // the same time of the abort, we should still report the finish back to the client.
-    if (mCancelled && status != AMEDIA_OK) {
+void MediaTranscoder::onThreadFinished(const void* thread, media_status_t threadStatus,
+                                       bool threadStopped) {
+    LOG(DEBUG) << "Thread " << thread << " finished with status " << threadStatus << " stopped "
+               << threadStopped;
+
+    // Stop all threads if one reports an error.
+    if (threadStatus != AMEDIA_OK) {
+        requestStop(false /* stopOnSync */);
+    }
+
+    std::scoped_lock lock{mThreadStateMutex};
+
+    // Record the change.
+    mThreadStates[thread] = DONE;
+    if (threadStatus != AMEDIA_OK && mTranscoderStatus == AMEDIA_OK) {
+        mTranscoderStatus = threadStatus;
+    }
+
+    mTranscoderStopped |= threadStopped;
+
+    // Check if all threads are done. Note that if all transcoders have stopped but the sample
+    // writer has not yet started, it never will.
+    bool transcodersDone = true;
+    ThreadState sampleWriterState = PENDING;
+    for (const auto& it : mThreadStates) {
+        LOG(DEBUG) << "  Thread " << it.first << " state" << it.second;
+        if (it.first == static_cast<const void*>(mSampleWriter.get())) {
+            sampleWriterState = it.second;
+        } else {
+            transcodersDone &= (it.second == DONE);
+        }
+    }
+    if (!transcodersDone || sampleWriterState == RUNNING) {
         return;
     }
 
-    bool expected = false;
-    if (mCallbackSent.compare_exchange_strong(expected, true)) {
-        if (status == AMEDIA_OK) {
-            mCallbacks->onFinished(this);
-        } else {
-            mCallbacks->onError(this, status);
-        }
-
-        // Transcoding is done and the callback to the client has been sent, so tear down the
-        // pipeline but do it asynchronously to avoid deadlocks. If an error occurred, client
-        // should clean up the file.
-        std::thread asyncCancelThread{[self = shared_from_this()] { self->cancel(); }};
-        asyncCancelThread.detach();
+    // All done. Send callback asynchronously and wake up threads waiting in cancel/pause.
+    mThreadsDone = true;
+    if (!mCallbackSent) {
+        std::thread asyncNotificationThread{[this, self = shared_from_this(),
+                                             status = mTranscoderStatus,
+                                             stopped = mTranscoderStopped] {
+            // If the transcoder was stopped that means a caller is waiting in stop or pause
+            // in which case we don't send a callback.
+            if (status != AMEDIA_OK) {
+                mCallbacks->onError(this, status);
+            } else if (!stopped) {
+                mCallbacks->onFinished(this);
+            }
+            mThreadsDoneSignal.notify_all();
+        }};
+        asyncNotificationThread.detach();
+        mCallbackSent = true;
     }
 }
 
 void MediaTranscoder::onTrackFormatAvailable(const MediaTrackTranscoder* transcoder) {
-    LOG(INFO) << "TrackTranscoder " << transcoder << " format available.";
+    LOG(DEBUG) << "TrackTranscoder " << transcoder << " format available.";
 
     std::scoped_lock lock{mTracksAddedMutex};
+    const void* sampleWriterPtr = static_cast<const void*>(mSampleWriter.get());
 
     // Ignore duplicate format change.
     if (mTracksAdded.count(transcoder) > 0) {
@@ -111,7 +140,7 @@
     auto consumer = mSampleWriter->addTrack(transcoder->getOutputFormat());
     if (consumer == nullptr) {
         LOG(ERROR) << "Unable to add track to sample writer.";
-        sendCallback(AMEDIA_ERROR_UNKNOWN);
+        onThreadFinished(sampleWriterPtr, AMEDIA_ERROR_UNKNOWN, false /* stopped */);
         return;
     }
 
@@ -119,34 +148,57 @@
     mutableTranscoder->setSampleConsumer(consumer);
 
     mTracksAdded.insert(transcoder);
+    bool errorStarting = false;
     if (mTracksAdded.size() == mTrackTranscoders.size()) {
         // Enable sequential access mode on the sample reader to achieve optimal read performance.
         // This has to wait until all tracks have delivered their output formats and the sample
         // writer is started. Otherwise the tracks will not get their output sample queues drained
         // and the transcoder could hang due to one track running out of buffers and blocking the
         // other tracks from reading source samples before they could output their formats.
-        mSampleReader->setEnforceSequentialAccess(true);
-        LOG(INFO) << "Starting sample writer.";
-        bool started = mSampleWriter->start();
-        if (!started) {
-            LOG(ERROR) << "Unable to start sample writer.";
-            sendCallback(AMEDIA_ERROR_UNKNOWN);
+
+        std::scoped_lock lock{mThreadStateMutex};
+        // Don't start the sample writer if a stop already has been requested.
+        if (!mSampleWriterStopped) {
+            if (!mCancelled) {
+                mSampleReader->setEnforceSequentialAccess(true);
+            }
+            LOG(DEBUG) << "Starting sample writer.";
+            errorStarting = !mSampleWriter->start();
+            if (!errorStarting) {
+                mThreadStates[sampleWriterPtr] = RUNNING;
+            }
         }
     }
+
+    if (errorStarting) {
+        LOG(ERROR) << "Unable to start sample writer.";
+        onThreadFinished(sampleWriterPtr, AMEDIA_ERROR_UNKNOWN, false /* stopped */);
+    }
 }
 
 void MediaTranscoder::onTrackFinished(const MediaTrackTranscoder* transcoder) {
     LOG(DEBUG) << "TrackTranscoder " << transcoder << " finished";
+    onThreadFinished(static_cast<const void*>(transcoder), AMEDIA_OK, false /* stopped */);
+}
+
+void MediaTranscoder::onTrackStopped(const MediaTrackTranscoder* transcoder) {
+    LOG(DEBUG) << "TrackTranscoder " << transcoder << " stopped";
+    onThreadFinished(static_cast<const void*>(transcoder), AMEDIA_OK, true /* stopped */);
 }
 
 void MediaTranscoder::onTrackError(const MediaTrackTranscoder* transcoder, media_status_t status) {
     LOG(ERROR) << "TrackTranscoder " << transcoder << " returned error " << status;
-    sendCallback(status);
+    onThreadFinished(static_cast<const void*>(transcoder), status, false /* stopped */);
 }
 
-void MediaTranscoder::onFinished(const MediaSampleWriter* writer __unused, media_status_t status) {
-    LOG((status != AMEDIA_OK) ? ERROR : DEBUG) << "Sample writer finished with status " << status;
-    sendCallback(status);
+void MediaTranscoder::onFinished(const MediaSampleWriter* writer, media_status_t status) {
+    LOG(status == AMEDIA_OK ? DEBUG : ERROR) << "Sample writer finished with status " << status;
+    onThreadFinished(static_cast<const void*>(writer), status, false /* stopped */);
+}
+
+void MediaTranscoder::onStopped(const MediaSampleWriter* writer) {
+    LOG(DEBUG) << "Sample writer " << writer << " stopped";
+    onThreadFinished(static_cast<const void*>(writer), AMEDIA_OK, true /* stopped */);
 }
 
 void MediaTranscoder::onProgressUpdate(const MediaSampleWriter* writer __unused, int32_t progress) {
@@ -277,6 +329,9 @@
         return status;
     }
 
+    std::scoped_lock lock{mThreadStateMutex};
+    mThreadStates[static_cast<const void*>(transcoder.get())] = PENDING;
+
     mTrackTranscoders.emplace_back(std::move(transcoder));
     return AMEDIA_OK;
 }
@@ -301,6 +356,8 @@
         return AMEDIA_ERROR_UNKNOWN;
     }
 
+    std::scoped_lock lock{mThreadStateMutex};
+    mThreadStates[static_cast<const void*>(mSampleWriter.get())] = PENDING;
     return AMEDIA_OK;
 }
 
@@ -314,21 +371,75 @@
     }
 
     // Start transcoders
-    for (auto& transcoder : mTrackTranscoders) {
-        bool started = transcoder->start();
-        if (!started) {
-            LOG(ERROR) << "Unable to start track transcoder.";
-            cancel();
-            return AMEDIA_ERROR_UNKNOWN;
+    bool started = true;
+    {
+        std::scoped_lock lock{mThreadStateMutex};
+        for (auto& transcoder : mTrackTranscoders) {
+            if (!(started = transcoder->start())) {
+                break;
+            }
+            mThreadStates[static_cast<const void*>(transcoder.get())] = RUNNING;
         }
     }
+    if (!started) {
+        LOG(ERROR) << "Unable to start track transcoder.";
+        cancel();
+        return AMEDIA_ERROR_UNKNOWN;
+    }
     return AMEDIA_OK;
 }
 
+media_status_t MediaTranscoder::requestStop(bool stopOnSync) {
+    std::scoped_lock lock{mThreadStateMutex};
+    if (mCancelled) {
+        LOG(DEBUG) << "MediaTranscoder already cancelled";
+        return AMEDIA_ERROR_UNSUPPORTED;
+    }
+
+    if (!stopOnSync) {
+        mSampleWriterStopped = true;
+        mSampleWriter->stop();
+    }
+
+    mSampleReader->setEnforceSequentialAccess(false);
+    for (auto& transcoder : mTrackTranscoders) {
+        transcoder->stop(stopOnSync);
+    }
+
+    mCancelled = true;
+    return AMEDIA_OK;
+}
+
+void MediaTranscoder::waitForThreads() NO_THREAD_SAFETY_ANALYSIS {
+    std::unique_lock lock{mThreadStateMutex};
+    while (!mThreadsDone) {
+        mThreadsDoneSignal.wait(lock);
+    }
+}
+
 media_status_t MediaTranscoder::pause(std::shared_ptr<ndk::ScopedAParcel>* pausedState) {
+    media_status_t status = requestStop(true /* stopOnSync */);
+    if (status != AMEDIA_OK) {
+        return status;
+    }
+
+    waitForThreads();
+
     // TODO: write internal states to parcel.
     *pausedState = std::shared_ptr<::ndk::ScopedAParcel>(new ::ndk::ScopedAParcel());
-    return cancel();
+    return AMEDIA_OK;
+}
+
+media_status_t MediaTranscoder::cancel() {
+    media_status_t status = requestStop(false /* stopOnSync */);
+    if (status != AMEDIA_OK) {
+        return status;
+    }
+
+    waitForThreads();
+
+    // TODO: Release transcoders?
+    return AMEDIA_OK;
 }
 
 media_status_t MediaTranscoder::resume() {
@@ -336,20 +447,4 @@
     return start();
 }
 
-media_status_t MediaTranscoder::cancel() {
-    bool expected = false;
-    if (!mCancelled.compare_exchange_strong(expected, true)) {
-        // Already cancelled.
-        return AMEDIA_OK;
-    }
-
-    mSampleWriter->stop();
-    mSampleReader->setEnforceSequentialAccess(false);
-    for (auto& transcoder : mTrackTranscoders) {
-        transcoder->stop();
-    }
-
-    return AMEDIA_OK;
-}
-
 }  // namespace android
diff --git a/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
index 35b1d33..c55e244 100644
--- a/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/PassthroughTrackTranscoder.cpp
@@ -93,9 +93,10 @@
     return AMEDIA_OK;
 }
 
-media_status_t PassthroughTrackTranscoder::runTranscodeLoop() {
+media_status_t PassthroughTrackTranscoder::runTranscodeLoop(bool* stopped) {
     MediaSampleInfo info;
     std::shared_ptr<MediaSample> sample;
+    bool eosReached = false;
 
     // Notify the track format as soon as we start. It's same as the source format.
     notifyTrackFormatAvailable();
@@ -106,18 +107,18 @@
             };
 
     // Move samples until EOS is reached or transcoding is stopped.
-    while (!mStopRequested && !mEosFromSource) {
+    while (mStopRequest != STOP_NOW && !eosReached) {
         media_status_t status = mMediaSampleReader->getSampleInfoForTrack(mTrackIndex, &info);
 
         if (status == AMEDIA_OK) {
             uint8_t* buffer = mBufferPool->getBufferWithSize(info.size);
             if (buffer == nullptr) {
-                if (mStopRequested) {
+                if (mStopRequest == STOP_NOW) {
                     break;
                 }
 
                 LOG(ERROR) << "Unable to get buffer from pool";
-                return AMEDIA_ERROR_IO;  // TODO: Custom error codes?
+                return AMEDIA_ERROR_UNKNOWN;
             }
 
             sample = MediaSample::createWithReleaseCallback(
@@ -131,7 +132,7 @@
 
         } else if (status == AMEDIA_ERROR_END_OF_STREAM) {
             sample = std::make_shared<MediaSample>();
-            mEosFromSource = true;
+            eosReached = true;
         } else {
             LOG(ERROR) << "Unable to get next sample info. Aborting transcode.";
             return status;
@@ -139,17 +140,22 @@
 
         sample->info = info;
         onOutputSampleAvailable(sample);
+
+        if (mStopRequest == STOP_ON_SYNC && info.flags & SAMPLE_FLAG_SYNC_SAMPLE) {
+            break;
+        }
     }
 
-    if (mStopRequested && !mEosFromSource) {
-        return AMEDIA_ERROR_UNKNOWN;  // TODO: Custom error codes?
+    if (mStopRequest != NONE && !eosReached) {
+        *stopped = true;
     }
     return AMEDIA_OK;
 }
 
 void PassthroughTrackTranscoder::abortTranscodeLoop() {
-    mStopRequested = true;
-    mBufferPool->abort();
+    if (mStopRequest == STOP_NOW) {
+        mBufferPool->abort();
+    }
 }
 
 std::shared_ptr<AMediaFormat> PassthroughTrackTranscoder::getOutputFormat() const {
diff --git a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
index c1456fd..5ec5e08 100644
--- a/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
+++ b/media/libmediatranscoding/transcoder/VideoTrackTranscoder.cpp
@@ -156,11 +156,7 @@
                 static_cast<VideoTrackTranscoder::CodecWrapper*>(userdata);
         if (auto transcoder = wrapper->getTranscoder()) {
             transcoder->mCodecMessageQueue.push(
-                    [transcoder, error] {
-                        transcoder->mStatus = error;
-                        transcoder->mStopRequested = true;
-                    },
-                    true);
+                    [transcoder, error] { transcoder->mStatus = error; }, true);
         }
     }
 };
@@ -406,6 +402,8 @@
         sample->info.presentationTimeUs = bufferInfo.presentationTimeUs;
 
         onOutputSampleAvailable(sample);
+
+        mLastSampleWasSync = sample->info.flags & SAMPLE_FLAG_SYNC_SAMPLE;
     } else if (bufferIndex == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
         AMediaFormat* newFormat = AMediaCodec_getOutputFormat(mEncoder->getCodec());
         LOG(DEBUG) << "Encoder output format changed: " << AMediaFormat_toString(newFormat);
@@ -485,7 +483,7 @@
     notifyTrackFormatAvailable();
 }
 
-media_status_t VideoTrackTranscoder::runTranscodeLoop() {
+media_status_t VideoTrackTranscoder::runTranscodeLoop(bool* stopped) {
     androidSetThreadPriority(0 /* tid (0 = current) */, ANDROID_PRIORITY_VIDEO);
 
     // Push start decoder and encoder as two messages, so that these are subject to the
@@ -509,25 +507,31 @@
     });
 
     // Process codec events until EOS is reached, transcoding is stopped or an error occurs.
-    while (!mStopRequested && !mEosFromEncoder && mStatus == AMEDIA_OK) {
+    while (mStopRequest != STOP_NOW && !mEosFromEncoder && mStatus == AMEDIA_OK) {
         std::function<void()> message = mCodecMessageQueue.pop();
         message();
+
+        if (mStopRequest == STOP_ON_SYNC && mLastSampleWasSync) {
+            break;
+        }
     }
 
     mCodecMessageQueue.abort();
     AMediaCodec_stop(mDecoder);
 
-    // Return error if transcoding was stopped before it finished.
-    if (mStopRequested && !mEosFromEncoder && mStatus == AMEDIA_OK) {
-        mStatus = AMEDIA_ERROR_UNKNOWN;  // TODO: Define custom error codes?
+    // Signal if transcoding was stopped before it finished.
+    if (mStopRequest != NONE && !mEosFromEncoder && mStatus == AMEDIA_OK) {
+        *stopped = true;
     }
 
     return mStatus;
 }
 
 void VideoTrackTranscoder::abortTranscodeLoop() {
-    // Push abort message to the front of the codec event queue.
-    mCodecMessageQueue.push([this] { mStopRequested = true; }, true /* front */);
+    if (mStopRequest == STOP_NOW) {
+        // Wake up transcoder thread.
+        mCodecMessageQueue.push([] {}, true /* front */);
+    }
 }
 
 std::shared_ptr<AMediaFormat> VideoTrackTranscoder::getOutputFormat() const {
diff --git a/media/libmediatranscoding/transcoder/benchmark/MediaTrackTranscoderBenchmark.cpp b/media/libmediatranscoding/transcoder/benchmark/MediaTrackTranscoderBenchmark.cpp
index aee0ed6..351d80b 100644
--- a/media/libmediatranscoding/transcoder/benchmark/MediaTrackTranscoderBenchmark.cpp
+++ b/media/libmediatranscoding/transcoder/benchmark/MediaTrackTranscoderBenchmark.cpp
@@ -61,6 +61,12 @@
         mCondition.notify_all();
     }
 
+    virtual void onTrackStopped(const MediaTrackTranscoder* transcoder __unused) override {
+        std::unique_lock lock(mMutex);
+        mFinished = true;
+        mCondition.notify_all();
+    }
+
     virtual void onTrackError(const MediaTrackTranscoder* transcoder __unused,
                               media_status_t status) override {
         std::unique_lock lock(mMutex);
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h b/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h
index f762556..080f2b7 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaSampleWriter.h
@@ -84,6 +84,9 @@
          */
         virtual void onFinished(const MediaSampleWriter* writer, media_status_t status) = 0;
 
+        /** Sample writer was stopped before it was finished. */
+        virtual void onStopped(const MediaSampleWriter* writer) = 0;
+
         /** Sample writer progress update in percent. */
         virtual void onProgressUpdate(const MediaSampleWriter* writer, int32_t progress) = 0;
 
@@ -129,15 +132,14 @@
     bool start();
 
     /**
-     * Stops the sample writer. If the sample writer is not yet finished its operation will be
-     * aborted and an error value will be returned to the client in the callback supplied to
-     * {@link #start}. If the sample writer has already finished and the client callback has fired
-     * the writer has already automatically stopped and there is no need to call stop manually. Once
-     * the sample writer has been stopped it cannot be restarted.
-     * @return True if the sample writer was successfully stopped on this call. False if the sample
-     *         writer was already stopped or was never started.
+     * Stops the sample writer. If the sample writer is not yet finished, its operation will be
+     * aborted and the onStopped callback will fire. If the sample writer has already finished and
+     * the onFinished callback has fired the writer has already automatically stopped and there is
+     * no need to call stop manually. Once the sample writer has been stopped it cannot be
+     * restarted. This method is asynchronous and will not wait for the sample writer to stop before
+     * returning.
      */
-    bool stop();
+    void stop();
 
     /** Destructor. */
     ~MediaSampleWriter();
@@ -186,7 +188,6 @@
 
     std::mutex mMutex;  // Protects sample queue and state.
     std::condition_variable mSampleSignal;
-    std::thread mThread;
     std::unordered_map<size_t, TrackRecord> mTracks;
     std::priority_queue<SampleEntry, std::vector<SampleEntry>, SampleComparator> mSampleQueue
             GUARDED_BY(mMutex);
@@ -200,8 +201,8 @@
 
     MediaSampleWriter() : mState(UNINITIALIZED){};
     void addSampleToTrack(size_t trackIndex, const std::shared_ptr<MediaSample>& sample);
-    media_status_t writeSamples();
-    media_status_t runWriterLoop();
+    media_status_t writeSamples(bool* wasStopped);
+    media_status_t runWriterLoop(bool* wasStopped);
 };
 
 }  // namespace android
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
index c5e161c..724b919 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoder.h
@@ -62,18 +62,21 @@
                              const std::shared_ptr<AMediaFormat>& destinationFormat);
 
     /**
-     * Starts the track transcoder. Once started the track transcoder have to be stopped by calling
-     * {@link #stop}, even after completing successfully. Start should only be called once.
+     * Starts the track transcoder. After the track transcoder is successfully started it will run
+     * until a callback signals that transcoding has ended. Start should only be called once.
      * @return True if the track transcoder started, or false if it had already been started.
      */
     bool start();
 
     /**
      * Stops the track transcoder. Once the transcoding has been stopped it cannot be restarted
-     * again. It is safe to call stop multiple times.
-     * @return True if the track transcoder stopped, or false if it was already stopped.
+     * again. It is safe to call stop multiple times. Stop is an asynchronous operation. Once the
+     * track transcoder has stopped the onTrackStopped callback will get called, unless the
+     * transcoding finished or encountered an error before it could be stopped in which case the
+     * callbacks corresponding to those events will be called instead.
+     * @param stopOnSyncSample Request the transcoder to stop after emitting a sync sample.
      */
-    bool stop();
+    void stop(bool stopOnSyncSample = false);
 
     /**
      * Set the sample consumer function. The MediaTrackTranscoder will deliver transcoded samples to
@@ -100,7 +103,9 @@
     // Called by subclasses when the actual track format becomes available.
     void notifyTrackFormatAvailable();
 
-    // Called by subclasses when a transcoded sample is available.
+    // Called by subclasses when a transcoded sample is available. Samples must not hold a strong
+    // reference to the track transcoder in order to avoid retain cycles through the track
+    // transcoder's sample queue.
     void onOutputSampleAvailable(const std::shared_ptr<MediaSample>& sample);
 
     // configureDestinationFormat needs to be implemented by subclasses, and gets called on an
@@ -110,7 +115,7 @@
 
     // runTranscodeLoop needs to be implemented by subclasses, and gets called on
     // MediaTrackTranscoder's internal thread when the track transcoder is started.
-    virtual media_status_t runTranscodeLoop() = 0;
+    virtual media_status_t runTranscodeLoop(bool* stopped) = 0;
 
     // abortTranscodeLoop needs to be implemented by subclasses, and should request transcoding to
     // be aborted as soon as possible. It should be safe to call abortTranscodeLoop multiple times.
@@ -120,13 +125,20 @@
     int mTrackIndex;
     std::shared_ptr<AMediaFormat> mSourceFormat;
 
+    enum StopRequest {
+        NONE,
+        STOP_NOW,
+        STOP_ON_SYNC,
+    };
+    std::atomic<StopRequest> mStopRequest = NONE;
+
 private:
     std::mutex mSampleMutex;
+    // SampleQueue for buffering output samples before a sample consumer has been set.
     MediaSampleQueue mSampleQueue GUARDED_BY(mSampleMutex);
     MediaSampleWriter::MediaSampleConsumerFunction mSampleConsumer GUARDED_BY(mSampleMutex);
     const std::weak_ptr<MediaTrackTranscoderCallback> mTranscoderCallback;
     std::mutex mStateMutex;
-    std::thread mTranscodingThread GUARDED_BY(mStateMutex);
     enum {
         UNINITIALIZED,
         CONFIGURED,
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoderCallback.h b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoderCallback.h
index 654171e..7b62d46 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoderCallback.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTrackTranscoderCallback.h
@@ -39,6 +39,12 @@
     virtual void onTrackFinished(const MediaTrackTranscoder* transcoder);
 
     /**
+     * Called when the MediaTrackTranscoder instance was explicitly stopped before it was finished.
+     * @param transcoder The MediaTrackTranscoder that was stopped.
+     */
+    virtual void onTrackStopped(const MediaTrackTranscoder* transcoder);
+
+    /**
      * Called when the MediaTrackTranscoder instance encountered an error it could not recover from.
      * @param transcoder The MediaTrackTranscoder that encountered the error.
      * @param status The non-zero error code describing the encountered error.
diff --git a/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
index 4bbb41a..4e11ef5 100644
--- a/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/MediaTranscoder.h
@@ -96,23 +96,25 @@
     media_status_t start();
 
     /**
-     * Pauses transcoding. The transcoder's paused state is returned through pausedState. The
-     * paused state is only needed for resuming transcoding with a new MediaTranscoder instance. The
-     * caller can resume transcoding with the current MediaTranscoder instance at any time by
-     * calling resume(). It is not required to cancel a paused transcoder. The paused state is
-     * independent and the caller can always initialize a new transcoder instance with the same
-     * paused state. If the caller wishes to abandon a paused transcoder's operation they can
-     * release the transcoder instance, clear the paused state and delete the partial destination
-     * file. The caller can optionally call cancel to let the transcoder clean up the partial
-     * destination file.
+     * Pauses transcoding and finalizes the partial transcoded file to disk. Pause is a synchronous
+     * operation and will wait until all internal components are done. Once this method returns it
+     * is safe to release the transcoder instance. No callback will be called if the transcoder was
+     * paused successfully. But if the transcoding finishes or encountered an error during pause,
+     * the corresponding callback will be called.
      */
     media_status_t pause(std::shared_ptr<ndk::ScopedAParcel>* pausedState);
 
     /** Resumes a paused transcoding. */
     media_status_t resume();
 
-    /** Cancels the transcoding. Once canceled the transcoding can not be restarted. Client
-     * will be responsible for cleaning up the abandoned file. */
+    /**
+     * Cancels the transcoding. Once canceled the transcoding can not be restarted. Client
+     * will be responsible for cleaning up the abandoned file. Cancel is a synchronous operation and
+     * will wait until all internal components are done. Once this method returns it is safe to
+     * release the transcoder instance. Normally no callback will be called when the transcoder is
+     * cancelled. But if the transcoding finishes or encountered an error during cancel, the
+     * corresponding callback will be called.
+     */
     media_status_t cancel();
 
     virtual ~MediaTranscoder() = default;
@@ -123,17 +125,20 @@
     // MediaTrackTranscoderCallback
     virtual void onTrackFormatAvailable(const MediaTrackTranscoder* transcoder) override;
     virtual void onTrackFinished(const MediaTrackTranscoder* transcoder) override;
+    virtual void onTrackStopped(const MediaTrackTranscoder* transcoder) override;
     virtual void onTrackError(const MediaTrackTranscoder* transcoder,
                               media_status_t status) override;
     // ~MediaTrackTranscoderCallback
 
     // MediaSampleWriter::CallbackInterface
     virtual void onFinished(const MediaSampleWriter* writer, media_status_t status) override;
+    virtual void onStopped(const MediaSampleWriter* writer) override;
     virtual void onProgressUpdate(const MediaSampleWriter* writer, int32_t progress) override;
     // ~MediaSampleWriter::CallbackInterface
 
-    void onSampleWriterFinished(media_status_t status);
-    void sendCallback(media_status_t status);
+    void onThreadFinished(const void* thread, media_status_t threadStatus, bool threadStopped);
+    media_status_t requestStop(bool stopOnSync);
+    void waitForThreads();
 
     std::shared_ptr<CallbackInterface> mCallbacks;
     std::shared_ptr<MediaSampleReader> mSampleReader;
@@ -145,7 +150,20 @@
     pid_t mPid;
     uid_t mUid;
 
-    std::atomic_bool mCallbackSent = false;
+    enum ThreadState {
+        PENDING = 0,  // Not yet started.
+        RUNNING,      // Currently running.
+        DONE,         // Done running (can be finished, stopped or error).
+    };
+    std::mutex mThreadStateMutex;
+    std::condition_variable mThreadsDoneSignal;
+    std::unordered_map<const void*, ThreadState> mThreadStates GUARDED_BY(mThreadStateMutex);
+    media_status_t mTranscoderStatus GUARDED_BY(mThreadStateMutex) = AMEDIA_OK;
+    bool mTranscoderStopped GUARDED_BY(mThreadStateMutex) = false;
+    bool mThreadsDone GUARDED_BY(mThreadStateMutex) = false;
+    bool mCallbackSent GUARDED_BY(mThreadStateMutex) = false;
+    bool mSampleWriterStopped GUARDED_BY(mThreadStateMutex) = false;
+
     std::atomic_bool mCancelled = false;
 };
 
diff --git a/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
index b9491ed..c074831 100644
--- a/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/PassthroughTrackTranscoder.h
@@ -86,7 +86,7 @@
     };
 
     // MediaTrackTranscoder
-    media_status_t runTranscodeLoop() override;
+    media_status_t runTranscodeLoop(bool* stopped) override;
     void abortTranscodeLoop() override;
     media_status_t configureDestinationFormat(
             const std::shared_ptr<AMediaFormat>& destinationFormat) override;
@@ -94,8 +94,6 @@
     // ~MediaTrackTranscoder
 
     std::shared_ptr<BufferPool> mBufferPool;
-    bool mEosFromSource = false;
-    std::atomic_bool mStopRequested = false;
 };
 
 }  // namespace android
diff --git a/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
index 33ae3ba..d2ffb01 100644
--- a/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
+++ b/media/libmediatranscoding/transcoder/include/media/VideoTrackTranscoder.h
@@ -67,7 +67,7 @@
           : MediaTrackTranscoder(transcoderCallback), mPid(pid), mUid(uid){};
 
     // MediaTrackTranscoder
-    media_status_t runTranscodeLoop() override;
+    media_status_t runTranscodeLoop(bool* stopped) override;
     void abortTranscodeLoop() override;
     media_status_t configureDestinationFormat(
             const std::shared_ptr<AMediaFormat>& destinationFormat) override;
@@ -91,7 +91,7 @@
     ANativeWindow* mSurface = nullptr;
     bool mEosFromSource = false;
     bool mEosFromEncoder = false;
-    bool mStopRequested = false;
+    bool mLastSampleWasSync = false;
     media_status_t mStatus = AMEDIA_OK;
     MediaSampleInfo mSampleInfo;
     BlockingQueue<std::function<void()>> mCodecMessageQueue;
diff --git a/media/libmediatranscoding/transcoder/tests/Android.bp b/media/libmediatranscoding/transcoder/tests/Android.bp
index 21e4221..d0ea802 100644
--- a/media/libmediatranscoding/transcoder/tests/Android.bp
+++ b/media/libmediatranscoding/transcoder/tests/Android.bp
@@ -12,6 +12,8 @@
     ],
     shared_libs: [
         "libbase",
+        "libbinder_ndk",
+        "libcrypto",
         "libcutils",
         "libmediandk",
         "libnativewindow",
@@ -55,7 +57,6 @@
     name: "MediaTrackTranscoderTests",
     defaults: ["testdefaults"],
     srcs: ["MediaTrackTranscoderTests.cpp"],
-    shared_libs: ["libbinder_ndk"],
 }
 
 // VideoTrackTranscoder unit test
@@ -70,7 +71,6 @@
     name: "PassthroughTrackTranscoderTests",
     defaults: ["testdefaults"],
     srcs: ["PassthroughTrackTranscoderTests.cpp"],
-    shared_libs: ["libcrypto"],
 }
 
 // MediaSampleWriter unit test
@@ -85,5 +85,4 @@
     name: "MediaTranscoderTests",
     defaults: ["testdefaults"],
     srcs: ["MediaTranscoderTests.cpp"],
-    shared_libs: ["libbinder_ndk"],
 }
diff --git a/media/libmediatranscoding/transcoder/tests/MediaSampleReaderNDKTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaSampleReaderNDKTests.cpp
index 9c9c8b5..11af0b1 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaSampleReaderNDKTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaSampleReaderNDKTests.cpp
@@ -25,39 +25,166 @@
 #include <fcntl.h>
 #include <gtest/gtest.h>
 #include <media/MediaSampleReaderNDK.h>
+#include <openssl/md5.h>
 #include <utils/Timers.h>
 
 #include <cmath>
 #include <mutex>
 #include <thread>
 
-// TODO(b/153453392): Test more asset types and validate sample data from readSampleDataForTrack.
-// TODO(b/153453392): Test for sequential and parallel (single thread and multi thread) access.
-// TODO(b/153453392): Test for switching between sequential and parallel access in different points
-//  of time.
+// TODO(b/153453392): Test more asset types (frame reordering?).
 
 namespace android {
 
 #define SEC_TO_USEC(s) ((s)*1000 * 1000)
 
+/** Helper class for comparing sample data using checksums. */
+class Sample {
+public:
+    Sample(uint32_t flags, int64_t timestamp, size_t size, const uint8_t* buffer)
+          : mFlags{flags}, mTimestamp{timestamp}, mSize{size} {
+        initChecksum(buffer);
+    }
+
+    Sample(AMediaExtractor* extractor) {
+        mFlags = AMediaExtractor_getSampleFlags(extractor);
+        mTimestamp = AMediaExtractor_getSampleTime(extractor);
+        mSize = static_cast<size_t>(AMediaExtractor_getSampleSize(extractor));
+
+        auto buffer = std::make_unique<uint8_t[]>(mSize);
+        AMediaExtractor_readSampleData(extractor, buffer.get(), mSize);
+
+        initChecksum(buffer.get());
+    }
+
+    void initChecksum(const uint8_t* buffer) {
+        MD5_CTX md5Ctx;
+        MD5_Init(&md5Ctx);
+        MD5_Update(&md5Ctx, buffer, mSize);
+        MD5_Final(mChecksum, &md5Ctx);
+    }
+
+    bool operator==(const Sample& rhs) const {
+        return mSize == rhs.mSize && mFlags == rhs.mFlags && mTimestamp == rhs.mTimestamp &&
+               memcmp(mChecksum, rhs.mChecksum, MD5_DIGEST_LENGTH) == 0;
+    }
+
+    uint32_t mFlags;
+    int64_t mTimestamp;
+    size_t mSize;
+    uint8_t mChecksum[MD5_DIGEST_LENGTH];
+};
+
+/** Constant for selecting all samples. */
+static constexpr int SAMPLE_COUNT_ALL = -1;
+
+/**
+ * Utility class to test different sample access patterns combined with sequential or parallel
+ * sample access modes.
+ */
+class SampleAccessTester {
+public:
+    SampleAccessTester(int sourceFd, size_t fileSize) {
+        mSampleReader = MediaSampleReaderNDK::createFromFd(sourceFd, 0, fileSize);
+        EXPECT_TRUE(mSampleReader);
+
+        mTrackCount = mSampleReader->getTrackCount();
+
+        for (int trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
+            EXPECT_EQ(mSampleReader->selectTrack(trackIndex), AMEDIA_OK);
+        }
+
+        mSamples.resize(mTrackCount);
+        mTrackThreads.resize(mTrackCount);
+    }
+
+    void getSampleInfo(int trackIndex) {
+        MediaSampleInfo info;
+        media_status_t status = mSampleReader->getSampleInfoForTrack(trackIndex, &info);
+        EXPECT_EQ(status, AMEDIA_OK);
+    }
+
+    void readSamplesAsync(int trackIndex, int sampleCount) {
+        mTrackThreads[trackIndex] = std::thread{[this, trackIndex, sampleCount] {
+            int samplesRead = 0;
+            MediaSampleInfo info;
+            while (samplesRead < sampleCount || sampleCount == SAMPLE_COUNT_ALL) {
+                media_status_t status = mSampleReader->getSampleInfoForTrack(trackIndex, &info);
+                if (status != AMEDIA_OK) {
+                    EXPECT_EQ(status, AMEDIA_ERROR_END_OF_STREAM);
+                    EXPECT_TRUE((info.flags & SAMPLE_FLAG_END_OF_STREAM) != 0);
+                    break;
+                }
+                ASSERT_TRUE((info.flags & SAMPLE_FLAG_END_OF_STREAM) == 0);
+
+                auto buffer = std::make_unique<uint8_t[]>(info.size);
+                status = mSampleReader->readSampleDataForTrack(trackIndex, buffer.get(), info.size);
+                EXPECT_EQ(status, AMEDIA_OK);
+
+                mSampleMutex.lock();
+                const uint8_t* bufferPtr = buffer.get();
+                mSamples[trackIndex].emplace_back(info.flags, info.presentationTimeUs, info.size,
+                                                  bufferPtr);
+                mSampleMutex.unlock();
+                ++samplesRead;
+            }
+        }};
+    }
+
+    void readSamplesAsync(int sampleCount) {
+        for (int trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
+            readSamplesAsync(trackIndex, sampleCount);
+        }
+    }
+
+    void waitForTrack(int trackIndex) {
+        ASSERT_TRUE(mTrackThreads[trackIndex].joinable());
+        mTrackThreads[trackIndex].join();
+    }
+
+    void waitForTracks() {
+        for (int trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
+            waitForTrack(trackIndex);
+        }
+    }
+
+    void setEnforceSequentialAccess(bool enforce) {
+        media_status_t status = mSampleReader->setEnforceSequentialAccess(enforce);
+        EXPECT_EQ(status, AMEDIA_OK);
+    }
+
+    std::vector<std::vector<Sample>>& getSamples() { return mSamples; }
+
+    std::shared_ptr<MediaSampleReader> mSampleReader;
+    size_t mTrackCount;
+    std::mutex mSampleMutex;
+    std::vector<std::thread> mTrackThreads;
+    std::vector<std::vector<Sample>> mSamples;
+};
+
 class MediaSampleReaderNDKTests : public ::testing::Test {
 public:
     MediaSampleReaderNDKTests() { LOG(DEBUG) << "MediaSampleReaderNDKTests created"; }
 
     void SetUp() override {
         LOG(DEBUG) << "MediaSampleReaderNDKTests set up";
+
+        // Need to start a thread pool to prevent AMediaExtractor binder calls from starving
+        // (b/155663561).
+        ABinderProcess_startThreadPool();
+
         const char* sourcePath =
                 "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
 
-        mExtractor = AMediaExtractor_new();
-        ASSERT_NE(mExtractor, nullptr);
-
         mSourceFd = open(sourcePath, O_RDONLY);
         ASSERT_GT(mSourceFd, 0);
 
         mFileSize = lseek(mSourceFd, 0, SEEK_END);
         lseek(mSourceFd, 0, SEEK_SET);
 
+        mExtractor = AMediaExtractor_new();
+        ASSERT_NE(mExtractor, nullptr);
+
         media_status_t status =
                 AMediaExtractor_setDataSourceFd(mExtractor, mSourceFd, 0, mFileSize);
         ASSERT_EQ(status, AMEDIA_OK);
@@ -68,14 +195,14 @@
         }
     }
 
-    void initExtractorTimestamps() {
-        // Save all sample timestamps, per track, as reported by the extractor.
-        mExtractorTimestamps.resize(mTrackCount);
+    void initExtractorSamples() {
+        if (mExtractorSamples.size() == mTrackCount) return;
+
+        // Save sample information, per track, as reported by the extractor.
+        mExtractorSamples.resize(mTrackCount);
         do {
             const int trackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
-            const int64_t sampleTime = AMediaExtractor_getSampleTime(mExtractor);
-
-            mExtractorTimestamps[trackIndex].push_back(sampleTime);
+            mExtractorSamples[trackIndex].emplace_back(mExtractor);
         } while (AMediaExtractor_advance(mExtractor));
 
         AMediaExtractor_seekTo(mExtractor, 0, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC);
@@ -104,6 +231,22 @@
         return bitrates;
     }
 
+    void compareSamples(std::vector<std::vector<Sample>>& readerSamples) {
+        initExtractorSamples();
+        EXPECT_EQ(readerSamples.size(), mTrackCount);
+
+        for (int trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
+            LOG(DEBUG) << "Track " << trackIndex << ", comparing "
+                       << readerSamples[trackIndex].size() << " samples.";
+            EXPECT_EQ(readerSamples[trackIndex].size(), mExtractorSamples[trackIndex].size());
+            for (size_t sampleIndex = 0; sampleIndex < readerSamples[trackIndex].size();
+                 sampleIndex++) {
+                EXPECT_EQ(readerSamples[trackIndex][sampleIndex],
+                          mExtractorSamples[trackIndex][sampleIndex]);
+            }
+        }
+    }
+
     void TearDown() override {
         LOG(DEBUG) << "MediaSampleReaderNDKTests tear down";
         AMediaExtractor_delete(mExtractor);
@@ -116,58 +259,91 @@
     size_t mTrackCount;
     int mSourceFd;
     size_t mFileSize;
-    std::vector<std::vector<int64_t>> mExtractorTimestamps;
+    std::vector<std::vector<Sample>> mExtractorSamples;
 };
 
-TEST_F(MediaSampleReaderNDKTests, TestSampleTimes) {
-    LOG(DEBUG) << "TestSampleTimes Starts";
+/** Reads all samples from all tracks in parallel. */
+TEST_F(MediaSampleReaderNDKTests, TestParallelSampleAccess) {
+    LOG(DEBUG) << "TestParallelSampleAccess Starts";
 
-    std::shared_ptr<MediaSampleReader> sampleReader =
-            MediaSampleReaderNDK::createFromFd(mSourceFd, 0, mFileSize);
-    ASSERT_TRUE(sampleReader);
+    SampleAccessTester tester{mSourceFd, mFileSize};
+    tester.readSamplesAsync(SAMPLE_COUNT_ALL);
+    tester.waitForTracks();
+    compareSamples(tester.getSamples());
+}
 
-    for (int trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
-        EXPECT_EQ(sampleReader->selectTrack(trackIndex), AMEDIA_OK);
-    }
+/** Reads all samples from all tracks sequentially. */
+TEST_F(MediaSampleReaderNDKTests, TestSequentialSampleAccess) {
+    LOG(DEBUG) << "TestSequentialSampleAccess Starts";
 
-    // Initialize the extractor timestamps.
-    initExtractorTimestamps();
+    SampleAccessTester tester{mSourceFd, mFileSize};
+    tester.setEnforceSequentialAccess(true);
+    tester.readSamplesAsync(SAMPLE_COUNT_ALL);
+    tester.waitForTracks();
+    compareSamples(tester.getSamples());
+}
 
-    std::mutex timestampMutex;
-    std::vector<std::thread> trackThreads;
-    std::vector<std::vector<int64_t>> readerTimestamps(mTrackCount);
+/** Reads all samples from one track in parallel mode before switching to sequential mode. */
+TEST_F(MediaSampleReaderNDKTests, TestMixedSampleAccessTrackEOS) {
+    LOG(DEBUG) << "TestMixedSampleAccessTrackEOS Starts";
 
-    for (int trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
-        trackThreads.emplace_back([sampleReader, trackIndex, &timestampMutex, &readerTimestamps] {
-            MediaSampleInfo info;
-            while (true) {
-                media_status_t status = sampleReader->getSampleInfoForTrack(trackIndex, &info);
-                if (status != AMEDIA_OK) {
-                    EXPECT_EQ(status, AMEDIA_ERROR_END_OF_STREAM);
-                    EXPECT_TRUE((info.flags & SAMPLE_FLAG_END_OF_STREAM) != 0);
-                    break;
-                }
-                ASSERT_TRUE((info.flags & SAMPLE_FLAG_END_OF_STREAM) == 0);
-                timestampMutex.lock();
-                readerTimestamps[trackIndex].push_back(info.presentationTimeUs);
-                timestampMutex.unlock();
-                sampleReader->advanceTrack(trackIndex);
+    for (int readSampleInfoFlag = 0; readSampleInfoFlag <= 1; readSampleInfoFlag++) {
+        for (int trackIndToEOS = 0; trackIndToEOS < mTrackCount; ++trackIndToEOS) {
+            LOG(DEBUG) << "Testing EOS of track " << trackIndToEOS;
+
+            SampleAccessTester tester{mSourceFd, mFileSize};
+
+            // If the flag is set, read sample info from a different track before draining the track
+            // under test to force the reader to save the extractor position.
+            if (readSampleInfoFlag) {
+                tester.getSampleInfo((trackIndToEOS + 1) % mTrackCount);
             }
-        });
-    }
 
-    for (auto& thread : trackThreads) {
-        thread.join();
-    }
+            // Read all samples from one track before enabling sequential access
+            tester.readSamplesAsync(trackIndToEOS, SAMPLE_COUNT_ALL);
+            tester.waitForTrack(trackIndToEOS);
+            tester.setEnforceSequentialAccess(true);
 
-    for (int trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
-        LOG(DEBUG) << "Track " << trackIndex << ", comparing "
-                   << readerTimestamps[trackIndex].size() << " samples.";
-        EXPECT_EQ(readerTimestamps[trackIndex].size(), mExtractorTimestamps[trackIndex].size());
-        for (size_t sampleIndex = 0; sampleIndex < readerTimestamps[trackIndex].size();
-             sampleIndex++) {
-            EXPECT_EQ(readerTimestamps[trackIndex][sampleIndex],
-                      mExtractorTimestamps[trackIndex][sampleIndex]);
+            for (int trackIndex = 0; trackIndex < mTrackCount; ++trackIndex) {
+                if (trackIndex == trackIndToEOS) continue;
+
+                tester.readSamplesAsync(trackIndex, SAMPLE_COUNT_ALL);
+                tester.waitForTrack(trackIndex);
+            }
+
+            compareSamples(tester.getSamples());
+        }
+    }
+}
+
+/**
+ * Reads different combinations of sample counts from all tracks in parallel mode before switching
+ * to sequential mode and reading the rest of the samples.
+ */
+TEST_F(MediaSampleReaderNDKTests, TestMixedSampleAccess) {
+    LOG(DEBUG) << "TestMixedSampleAccess Starts";
+    initExtractorSamples();
+
+    for (int trackIndToTest = 0; trackIndToTest < mTrackCount; ++trackIndToTest) {
+        for (int sampleCount = 0; sampleCount <= (mExtractorSamples[trackIndToTest].size() + 1);
+             ++sampleCount) {
+            SampleAccessTester tester{mSourceFd, mFileSize};
+
+            for (int trackIndex = 0; trackIndex < mTrackCount; ++trackIndex) {
+                if (trackIndex == trackIndToTest) {
+                    tester.readSamplesAsync(trackIndex, sampleCount);
+                } else {
+                    tester.readSamplesAsync(trackIndex, mExtractorSamples[trackIndex].size() / 2);
+                }
+            }
+
+            tester.waitForTracks();
+            tester.setEnforceSequentialAccess(true);
+
+            tester.readSamplesAsync(SAMPLE_COUNT_ALL);
+            tester.waitForTracks();
+
+            compareSamples(tester.getSamples());
         }
     }
 }
diff --git a/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp
index 46f3e9b..0a41b00 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaSampleWriterTests.cpp
@@ -179,8 +179,6 @@
 
 class TestCallbacks : public MediaSampleWriter::CallbackInterface {
 public:
-    TestCallbacks(bool expectSuccess = true) : mExpectSuccess(expectSuccess) {}
-
     bool hasFinished() {
         std::unique_lock<std::mutex> lock(mMutex);
         return mFinished;
@@ -191,12 +189,15 @@
                             media_status_t status) override {
         std::unique_lock<std::mutex> lock(mMutex);
         EXPECT_FALSE(mFinished);
-        if (mExpectSuccess) {
-            EXPECT_EQ(status, AMEDIA_OK);
-        } else {
-            EXPECT_NE(status, AMEDIA_OK);
-        }
         mFinished = true;
+        mStatus = status;
+        mCondition.notify_all();
+    }
+
+    virtual void onStopped(const MediaSampleWriter* writer __unused) {
+        std::unique_lock<std::mutex> lock(mMutex);
+        EXPECT_FALSE(mFinished);
+        mStopped = true;
         mCondition.notify_all();
     }
 
@@ -213,18 +214,20 @@
 
     void waitForWritingFinished() {
         std::unique_lock<std::mutex> lock(mMutex);
-        while (!mFinished) {
+        while (!mFinished && !mStopped) {
             mCondition.wait(lock);
         }
     }
 
     uint32_t getProgressUpdateCount() const { return mProgressUpdateCount; }
+    bool wasStopped() const { return mStopped; }
 
 private:
     std::mutex mMutex;
     std::condition_variable mCondition;
     bool mFinished = false;
-    bool mExpectSuccess;
+    bool mStopped = false;
+    media_status_t mStatus = AMEDIA_OK;
     int32_t mLastProgress = -1;
     uint32_t mProgressUpdateCount = 0;
 };
@@ -316,8 +319,7 @@
 TEST_F(MediaSampleWriterTests, TestDoubleStartStop) {
     std::shared_ptr<MediaSampleWriter> writer = MediaSampleWriter::Create();
 
-    std::shared_ptr<TestCallbacks> callbacks =
-            std::make_shared<TestCallbacks>(false /* expectSuccess */);
+    std::shared_ptr<TestCallbacks> callbacks = std::make_shared<TestCallbacks>();
     EXPECT_TRUE(writer->init(mTestMuxer, callbacks));
 
     const TestMediaSource& mediaSource = getMediaSource();
@@ -327,9 +329,10 @@
     ASSERT_TRUE(writer->start());
     EXPECT_FALSE(writer->start());
 
-    EXPECT_TRUE(writer->stop());
-    EXPECT_TRUE(callbacks->hasFinished());
-    EXPECT_FALSE(writer->stop());
+    writer->stop();
+    writer->stop();
+    callbacks->waitForWritingFinished();
+    EXPECT_TRUE(callbacks->wasStopped());
 }
 
 TEST_F(MediaSampleWriterTests, TestStopWithoutStart) {
@@ -340,7 +343,7 @@
     EXPECT_NE(writer->addTrack(mediaSource.mTrackFormats[0]), nullptr);
     EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::AddTrack(mediaSource.mTrackFormats[0].get()));
 
-    EXPECT_FALSE(writer->stop());
+    writer->stop();
     EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::NoEvent);
 }
 
@@ -468,7 +471,6 @@
     }
 
     EXPECT_EQ(mTestMuxer->popEvent(), TestMuxer::Stop());
-    EXPECT_TRUE(writer->stop());
     EXPECT_TRUE(mTestCallbacks->hasFinished());
 }
 
@@ -541,7 +543,6 @@
 
     // Wait for writer.
     mTestCallbacks->waitForWritingFinished();
-    EXPECT_TRUE(writer->stop());
 
     // Compare output file with source.
     mediaSource.reset();
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
index 83f0a4a..21f0b86 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaTrackTranscoderTests.cpp
@@ -61,13 +61,10 @@
         }
         ASSERT_NE(mTranscoder, nullptr);
 
-        initSampleReader();
+        initSampleReader("/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4");
     }
 
-    void initSampleReader() {
-        const char* sourcePath =
-                "/data/local/tmp/TranscodingTestAssets/cubicle_avc_480x240_aac_24KHz.mp4";
-
+    void initSampleReader(const char* sourcePath) {
         const int sourceFd = open(sourcePath, O_RDONLY);
         ASSERT_GT(sourceFd, 0);
 
@@ -157,16 +154,23 @@
     ASSERT_TRUE(mTranscoder->start());
     drainOutputSamples();
     EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
-    EXPECT_TRUE(mTranscoder->stop());
+    EXPECT_TRUE(mCallback->transcodingFinished());
     EXPECT_TRUE(mGotEndOfStream);
 }
 
 TEST_P(MediaTrackTranscoderTests, StopNormalOperation) {
     LOG(DEBUG) << "Testing StopNormalOperation";
+
+    // Use a longer test asset to make sure that transcoding can be stopped.
+    initSampleReader("/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4");
+
     EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
               AMEDIA_OK);
     EXPECT_TRUE(mTranscoder->start());
-    EXPECT_TRUE(mTranscoder->stop());
+    mCallback->waitUntilTrackFormatAvailable();
+    mTranscoder->stop();
+    EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
+    EXPECT_TRUE(mCallback->transcodingWasStopped());
 }
 
 TEST_P(MediaTrackTranscoderTests, StartWithoutConfigure) {
@@ -178,17 +182,23 @@
     LOG(DEBUG) << "Testing StopWithoutStart";
     EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
               AMEDIA_OK);
-    EXPECT_FALSE(mTranscoder->stop());
+    mTranscoder->stop();
 }
 
 TEST_P(MediaTrackTranscoderTests, DoubleStartStop) {
     LOG(DEBUG) << "Testing DoubleStartStop";
+
+    // Use a longer test asset to make sure that transcoding can be stopped.
+    initSampleReader("/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4");
+
     EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
               AMEDIA_OK);
     EXPECT_TRUE(mTranscoder->start());
     EXPECT_FALSE(mTranscoder->start());
-    EXPECT_TRUE(mTranscoder->stop());
-    EXPECT_FALSE(mTranscoder->stop());
+    mTranscoder->stop();
+    mTranscoder->stop();
+    EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
+    EXPECT_TRUE(mCallback->transcodingWasStopped());
 }
 
 TEST_P(MediaTrackTranscoderTests, DoubleConfigure) {
@@ -212,7 +222,8 @@
     EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
               AMEDIA_OK);
     EXPECT_TRUE(mTranscoder->start());
-    EXPECT_TRUE(mTranscoder->stop());
+    mTranscoder->stop();
+    EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
     EXPECT_FALSE(mTranscoder->start());
 }
 
@@ -223,7 +234,7 @@
     ASSERT_TRUE(mTranscoder->start());
     drainOutputSamples();
     EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
-    EXPECT_TRUE(mTranscoder->stop());
+    mTranscoder->stop();
     EXPECT_FALSE(mTranscoder->start());
     EXPECT_TRUE(mGotEndOfStream);
 }
@@ -235,7 +246,7 @@
     ASSERT_TRUE(mTranscoder->start());
     drainOutputSamples(1 /* numSamplesToSave */);
     EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
-    EXPECT_TRUE(mTranscoder->stop());
+    mTranscoder->stop();
     EXPECT_TRUE(mGotEndOfStream);
 
     mTranscoder.reset();
@@ -251,7 +262,8 @@
     ASSERT_TRUE(mTranscoder->start());
     drainOutputSamples(1 /* numSamplesToSave */);
     mSamplesSavedSemaphore.wait();
-    EXPECT_TRUE(mTranscoder->stop());
+    mTranscoder->stop();
+    EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
 
     std::this_thread::sleep_for(std::chrono::milliseconds(20));
     mSavedSamples.clear();
@@ -272,6 +284,44 @@
               AMEDIA_OK);
 }
 
+TEST_P(MediaTrackTranscoderTests, StopOnSync) {
+    LOG(DEBUG) << "Testing StopOnSync";
+
+    // Use a longer test asset to make sure there is a GOP to finish.
+    initSampleReader("/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4");
+
+    EXPECT_EQ(mTranscoder->configure(mMediaSampleReader, mTrackIndex, mDestinationFormat),
+              AMEDIA_OK);
+
+    bool lastSampleWasEos = false;
+    bool lastRealSampleWasSync = false;
+    OneShotSemaphore samplesReceivedSemaphore;
+    uint32_t sampleCount = 0;
+
+    mTranscoder->setSampleConsumer([&](const std::shared_ptr<MediaSample>& sample) {
+        ASSERT_NE(sample, nullptr);
+
+        if ((lastSampleWasEos = sample->info.flags & SAMPLE_FLAG_END_OF_STREAM)) {
+            samplesReceivedSemaphore.signal();
+            return;
+        }
+        lastRealSampleWasSync = sample->info.flags & SAMPLE_FLAG_SYNC_SAMPLE;
+
+        if (++sampleCount >= 10) {  // Wait for a few samples before stopping.
+            samplesReceivedSemaphore.signal();
+        }
+    });
+
+    ASSERT_TRUE(mTranscoder->start());
+    samplesReceivedSemaphore.wait();
+    mTranscoder->stop(true /* stopOnSync */);
+    EXPECT_EQ(mCallback->waitUntilFinished(), AMEDIA_OK);
+
+    EXPECT_TRUE(lastSampleWasEos);
+    EXPECT_TRUE(lastRealSampleWasSync);
+    EXPECT_TRUE(mCallback->transcodingWasStopped());
+}
+
 };  // namespace android
 
 using namespace android;
diff --git a/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
index f813a5c..56f3e99 100644
--- a/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/MediaTranscoderTests.cpp
@@ -99,11 +99,11 @@
         }
     }
     media_status_t mStatus = AMEDIA_OK;
+    bool mFinished = false;
 
 private:
     std::mutex mMutex;
     std::condition_variable mCondition;
-    bool mFinished = false;
     bool mProgressMade = false;
 };
 
@@ -145,6 +145,8 @@
         kRunToCompletion,
         kCancelAfterProgress,
         kCancelAfterStart,
+        kPauseAfterProgress,
+        kPauseAfterStart,
     } TranscodeExecutionControl;
 
     using FormatConfigurationCallback = std::function<AMediaFormat*(AMediaFormat*)>;
@@ -181,7 +183,10 @@
 
         media_status_t startStatus = transcoder->start();
         EXPECT_EQ(startStatus, AMEDIA_OK);
+
         if (startStatus == AMEDIA_OK) {
+            std::shared_ptr<ndk::ScopedAParcel> pausedState;
+
             switch (executionControl) {
             case kCancelAfterProgress:
                 mCallbacks->waitForProgressMade();
@@ -189,6 +194,12 @@
             case kCancelAfterStart:
                 transcoder->cancel();
                 break;
+            case kPauseAfterProgress:
+                mCallbacks->waitForProgressMade();
+                FALLTHROUGH_INTENDED;
+            case kPauseAfterStart:
+                transcoder->pause(&pausedState);
+                break;
             case kRunToCompletion:
             default:
                 mCallbacks->waitForTranscodingFinished();
@@ -360,9 +371,10 @@
     const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
     const char* destPath = "/data/local/tmp/MediaTranscoder_Cancel.MP4";
 
-    for (int i = 0; i < 32; ++i) {
+    for (int i = 0; i < 20; ++i) {
         EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kCancelAfterProgress),
                   AMEDIA_OK);
+        EXPECT_FALSE(mCallbacks->mFinished);
         mCallbacks = std::make_shared<TestCallbacks>();
     }
 }
@@ -371,9 +383,34 @@
     const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
     const char* destPath = "/data/local/tmp/MediaTranscoder_Cancel.MP4";
 
-    for (int i = 0; i < 32; ++i) {
+    for (int i = 0; i < 20; ++i) {
         EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kCancelAfterStart),
                   AMEDIA_OK);
+        EXPECT_FALSE(mCallbacks->mFinished);
+        mCallbacks = std::make_shared<TestCallbacks>();
+    }
+}
+
+TEST_F(MediaTranscoderTests, TestPauseAfterProgress) {
+    const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
+    const char* destPath = "/data/local/tmp/MediaTranscoder_Pause.MP4";
+
+    for (int i = 0; i < 20; ++i) {
+        EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kPauseAfterProgress),
+                  AMEDIA_OK);
+        EXPECT_FALSE(mCallbacks->mFinished);
+        mCallbacks = std::make_shared<TestCallbacks>();
+    }
+}
+
+TEST_F(MediaTranscoderTests, TestPauseAfterStart) {
+    const char* srcPath = "/data/local/tmp/TranscodingTestAssets/longtest_15s.mp4";
+    const char* destPath = "/data/local/tmp/MediaTranscoder_Pause.MP4";
+
+    for (int i = 0; i < 20; ++i) {
+        EXPECT_EQ(transcodeHelper(srcPath, destPath, getAVCVideoFormat, kPauseAfterStart),
+                  AMEDIA_OK);
+        EXPECT_FALSE(mCallbacks->mFinished);
         mCallbacks = std::make_shared<TestCallbacks>();
     }
 }
diff --git a/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
index 9713e17..5071efd 100644
--- a/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/PassthroughTrackTranscoderTests.cpp
@@ -183,7 +183,6 @@
 
     callback->waitUntilFinished();
     EXPECT_EQ(sampleCount, sampleChecksums.size());
-    EXPECT_TRUE(transcoder.stop());
 }
 
 /** Class for testing PassthroughTrackTranscoder's built in buffer pool. */
diff --git a/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
index 8d05353..a782f71 100644
--- a/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
+++ b/media/libmediatranscoding/transcoder/tests/TrackTranscoderTestUtils.h
@@ -33,20 +33,14 @@
             AMediaFormat* sourceFormat, bool includeBitrate = true) {
         // Default video destination format setup.
         static constexpr float kFrameRate = 30.0f;
-        static constexpr float kIFrameInterval = 30.0f;
         static constexpr int32_t kBitRate = 2 * 1000 * 1000;
-        static constexpr int32_t kColorFormatSurface = 0x7f000789;
 
         AMediaFormat* destinationFormat = AMediaFormat_new();
         AMediaFormat_copy(destinationFormat, sourceFormat);
         AMediaFormat_setFloat(destinationFormat, AMEDIAFORMAT_KEY_FRAME_RATE, kFrameRate);
-        AMediaFormat_setFloat(destinationFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL,
-                              kIFrameInterval);
         if (includeBitrate) {
             AMediaFormat_setInt32(destinationFormat, AMEDIAFORMAT_KEY_BIT_RATE, kBitRate);
         }
-        AMediaFormat_setInt32(destinationFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT,
-                              kColorFormatSurface);
 
         return std::shared_ptr<AMediaFormat>(destinationFormat, &AMediaFormat_delete);
     }
@@ -70,6 +64,13 @@
         mTranscodingFinishedCondition.notify_all();
     }
 
+    virtual void onTrackStopped(const MediaTrackTranscoder* transcoder __unused) override {
+        std::unique_lock<std::mutex> lock(mMutex);
+        mTranscodingFinished = true;
+        mTranscodingStopped = true;
+        mTranscodingFinishedCondition.notify_all();
+    }
+
     void onTrackError(const MediaTrackTranscoder* transcoder __unused, media_status_t status) {
         std::unique_lock<std::mutex> lock(mMutex);
         mTranscodingFinished = true;
@@ -93,12 +94,18 @@
         }
     }
 
+    bool transcodingWasStopped() const { return mTranscodingFinished && mTranscodingStopped; }
+    bool transcodingFinished() const {
+        return mTranscodingFinished && !mTranscodingStopped && mStatus == AMEDIA_OK;
+    }
+
 private:
     media_status_t mStatus = AMEDIA_OK;
     std::mutex mMutex;
     std::condition_variable mTranscodingFinishedCondition;
     std::condition_variable mTrackFormatAvailableCondition;
     bool mTranscodingFinished = false;
+    bool mTranscodingStopped = false;
     bool mTrackFormatAvailable = false;
 };
 
diff --git a/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
index 1b5bd13..4ede97f 100644
--- a/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
+++ b/media/libmediatranscoding/transcoder/tests/VideoTrackTranscoderTests.cpp
@@ -135,7 +135,6 @@
     });
 
     EXPECT_EQ(callback->waitUntilFinished(), AMEDIA_OK);
-    EXPECT_TRUE(transcoder->stop());
 }
 
 TEST_F(VideoTrackTranscoderTests, PreserveBitrate) {
@@ -160,7 +159,8 @@
     auto outputFormat = transcoder->getOutputFormat();
     ASSERT_NE(outputFormat, nullptr);
 
-    ASSERT_TRUE(transcoder->stop());
+    transcoder->stop();
+    EXPECT_EQ(callback->waitUntilFinished(), AMEDIA_OK);
 
     int32_t outBitrate;
     EXPECT_TRUE(AMediaFormat_getInt32(outputFormat.get(), AMEDIAFORMAT_KEY_BIT_RATE, &outBitrate));
@@ -205,7 +205,8 @@
     // Wait for the encoder to output samples before stopping and releasing the transcoder.
     semaphore.wait();
 
-    EXPECT_TRUE(transcoder->stop());
+    transcoder->stop();
+    EXPECT_EQ(callback->waitUntilFinished(), AMEDIA_OK);
     transcoder.reset();
 
     // Return buffers to the codec so that it can resume processing, but keep one buffer to avoid
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index 44ee2ac..71c1b0b 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -5350,6 +5350,34 @@
                     if (mChannelMaskPresent) {
                         notify->setInt32("channel-mask", mChannelMask);
                     }
+
+                    if (!mIsEncoder && portIndex == kPortIndexOutput) {
+                        AString mime;
+                        if (mConfigFormat->findString("mime", &mime)
+                                && !strcasecmp(MEDIA_MIMETYPE_AUDIO_AAC, mime.c_str())) {
+
+                            OMX_AUDIO_PARAM_ANDROID_AACDRCPRESENTATIONTYPE presentation;
+                            InitOMXParams(&presentation);
+                            err = mOMXNode->getParameter(
+                                    (OMX_INDEXTYPE)OMX_IndexParamAudioAndroidAacDrcPresentation,
+                                    &presentation, sizeof(presentation));
+                            if (err != OK) {
+                                return err;
+                            }
+                            notify->setInt32("aac-encoded-target-level",
+                                             presentation.nEncodedTargetLevel);
+                            notify->setInt32("aac-drc-cut-level", presentation.nDrcCut);
+                            notify->setInt32("aac-drc-boost-level", presentation.nDrcBoost);
+                            notify->setInt32("aac-drc-heavy-compression",
+                                             presentation.nHeavyCompression);
+                            notify->setInt32("aac-target-ref-level",
+                                             presentation.nTargetReferenceLevel);
+                            notify->setInt32("aac-drc-effect-type", presentation.nDrcEffectType);
+                            notify->setInt32("aac-drc-album-mode", presentation.nDrcAlbumMode);
+                            notify->setInt32("aac-drc-output-loudness",
+                                             presentation.nDrcOutputLoudness);
+                        }
+                    }
                     break;
                 }
 
@@ -7810,6 +7838,58 @@
     // Ignore errors as failure is expected for codecs that aren't video encoders.
     (void)configureTemporalLayers(params, false /* inConfigure */, mOutputFormat);
 
+    AString mime;
+    if (!mIsEncoder
+            && (mConfigFormat->findString("mime", &mime))
+            && !strcasecmp(MEDIA_MIMETYPE_AUDIO_AAC, mime.c_str())) {
+        OMX_AUDIO_PARAM_ANDROID_AACDRCPRESENTATIONTYPE presentation;
+        InitOMXParams(&presentation);
+        mOMXNode->getParameter(
+                    (OMX_INDEXTYPE)OMX_IndexParamAudioAndroidAacDrcPresentation,
+                    &presentation, sizeof(presentation));
+        int32_t value32 = 0;
+        bool updated = false;
+        if (params->findInt32("aac-pcm-limiter-enable", &value32)) {
+            presentation.nPCMLimiterEnable = value32;
+            updated = true;
+        }
+        if (params->findInt32("aac-encoded-target-level", &value32)) {
+            presentation.nEncodedTargetLevel = value32;
+            updated = true;
+        }
+        if (params->findInt32("aac-drc-cut-level", &value32)) {
+            presentation.nDrcCut = value32;
+            updated = true;
+        }
+        if (params->findInt32("aac-drc-boost-level", &value32)) {
+            presentation.nDrcBoost = value32;
+            updated = true;
+        }
+        if (params->findInt32("aac-drc-heavy-compression", &value32)) {
+            presentation.nHeavyCompression = value32;
+            updated = true;
+        }
+        if (params->findInt32("aac-target-ref-level", &value32)) {
+            presentation.nTargetReferenceLevel = value32;
+            updated = true;
+        }
+        if (params->findInt32("aac-drc-effect-type", &value32)) {
+            presentation.nDrcEffectType = value32;
+            updated = true;
+        }
+        if (params->findInt32("aac-drc-album-mode", &value32)) {
+            presentation.nDrcAlbumMode = value32;
+            updated = true;
+        }
+        if (!params->findInt32("aac-drc-output-loudness", &value32)) {
+            presentation.nDrcOutputLoudness = value32;
+            updated = true;
+        }
+        if (updated) {
+            mOMXNode->setParameter((OMX_INDEXTYPE)OMX_IndexParamAudioAndroidAacDrcPresentation,
+                &presentation, sizeof(presentation));
+        }
+    }
     return setVendorParameters(params);
 }
 
diff --git a/media/libstagefright/codecs/aacdec/SoftAAC2.cpp b/media/libstagefright/codecs/aacdec/SoftAAC2.cpp
index 2aeddd7..28a7a1e 100644
--- a/media/libstagefright/codecs/aacdec/SoftAAC2.cpp
+++ b/media/libstagefright/codecs/aacdec/SoftAAC2.cpp
@@ -38,6 +38,7 @@
 #define DRC_DEFAULT_MOBILE_DRC_HEAVY 1   /* switch for heavy compression for mobile conf */
 #define DRC_DEFAULT_MOBILE_DRC_EFFECT 3  /* MPEG-D DRC effect type; 3 => Limited playback range */
 #define DRC_DEFAULT_MOBILE_DRC_ALBUM 0  /* MPEG-D DRC album mode; 0 => album mode is disabled, 1 => album mode is enabled */
+#define DRC_DEFAULT_MOBILE_OUTPUT_LOUDNESS -1 /* decoder output loudness; -1 => the value is unknown, otherwise dB step value (e.g. 64 for -16 dB) */
 #define DRC_DEFAULT_MOBILE_ENC_LEVEL (-1) /* encoder target level; -1 => the value is unknown, otherwise dB step value (e.g. 64 for -16 dB) */
 #define MAX_CHANNEL_COUNT            8  /* maximum number of audio channels that can be decoded */
 // names of properties that can be used to override the default DRC settings
@@ -230,6 +231,15 @@
     // For seven and eight channel input streams, enable 6.1 and 7.1 channel output
     aacDecoder_SetParam(mAACDecoder, AAC_PCM_MAX_OUTPUT_CHANNELS, -1);
 
+    mDrcCompressMode = DRC_DEFAULT_MOBILE_DRC_HEAVY;
+    mDrcTargetRefLevel = DRC_DEFAULT_MOBILE_REF_LEVEL;
+    mDrcEncTargetLevel = DRC_DEFAULT_MOBILE_ENC_LEVEL;
+    mDrcBoostFactor = DRC_DEFAULT_MOBILE_DRC_BOOST;
+    mDrcAttenuationFactor = DRC_DEFAULT_MOBILE_DRC_CUT;
+    mDrcEffectType = DRC_DEFAULT_MOBILE_DRC_EFFECT;
+    mDrcAlbumMode = DRC_DEFAULT_MOBILE_DRC_ALBUM;
+    mDrcOutputLoudness = DRC_DEFAULT_MOBILE_OUTPUT_LOUDNESS;
+
     return status;
 }
 
@@ -358,6 +368,27 @@
             return OMX_ErrorNone;
         }
 
+        case OMX_IndexParamAudioAndroidAacDrcPresentation:
+        {
+             OMX_AUDIO_PARAM_ANDROID_AACDRCPRESENTATIONTYPE *aacPresParams =
+                    (OMX_AUDIO_PARAM_ANDROID_AACDRCPRESENTATIONTYPE *)params;
+
+            ALOGD("get OMX_IndexParamAudioAndroidAacDrcPresentation");
+
+            if (!isValidOMXParam(aacPresParams)) {
+                return OMX_ErrorBadParameter;
+            }
+            aacPresParams->nDrcEffectType = mDrcEffectType;
+            aacPresParams->nDrcAlbumMode = mDrcAlbumMode;
+            aacPresParams->nDrcBoost =  mDrcBoostFactor;
+            aacPresParams->nDrcCut = mDrcAttenuationFactor;
+            aacPresParams->nHeavyCompression = mDrcCompressMode;
+            aacPresParams->nTargetReferenceLevel = mDrcTargetRefLevel;
+            aacPresParams->nEncodedTargetLevel = mDrcEncTargetLevel;
+            aacPresParams ->nDrcOutputLoudness = mDrcOutputLoudness;
+            return OMX_ErrorNone;
+        }
+
         default:
             return SimpleSoftOMXComponent::internalGetParameter(index, params);
     }
@@ -464,11 +495,13 @@
             if (aacPresParams->nDrcEffectType >= -1) {
                 ALOGV("set nDrcEffectType=%d", aacPresParams->nDrcEffectType);
                 aacDecoder_SetParam(mAACDecoder, AAC_UNIDRC_SET_EFFECT, aacPresParams->nDrcEffectType);
+                mDrcEffectType = aacPresParams->nDrcEffectType;
             }
             if (aacPresParams->nDrcAlbumMode >= -1) {
                 ALOGV("set nDrcAlbumMode=%d", aacPresParams->nDrcAlbumMode);
                 aacDecoder_SetParam(mAACDecoder, AAC_UNIDRC_ALBUM_MODE,
                         aacPresParams->nDrcAlbumMode);
+                mDrcAlbumMode = aacPresParams->nDrcAlbumMode;
             }
             bool updateDrcWrapper = false;
             if (aacPresParams->nDrcBoost >= 0) {
@@ -476,34 +509,42 @@
                 mDrcWrap.setParam(DRC_PRES_MODE_WRAP_DESIRED_BOOST_FACTOR,
                         aacPresParams->nDrcBoost);
                 updateDrcWrapper = true;
+                mDrcBoostFactor = aacPresParams->nDrcBoost;
             }
             if (aacPresParams->nDrcCut >= 0) {
                 ALOGV("set nDrcCut=%d", aacPresParams->nDrcCut);
                 mDrcWrap.setParam(DRC_PRES_MODE_WRAP_DESIRED_ATT_FACTOR, aacPresParams->nDrcCut);
                 updateDrcWrapper = true;
+                mDrcAttenuationFactor = aacPresParams->nDrcCut;
             }
             if (aacPresParams->nHeavyCompression >= 0) {
                 ALOGV("set nHeavyCompression=%d", aacPresParams->nHeavyCompression);
                 mDrcWrap.setParam(DRC_PRES_MODE_WRAP_DESIRED_HEAVY,
                         aacPresParams->nHeavyCompression);
                 updateDrcWrapper = true;
+                mDrcCompressMode = aacPresParams->nHeavyCompression;
             }
             if (aacPresParams->nTargetReferenceLevel >= -1) {
                 ALOGV("set nTargetReferenceLevel=%d", aacPresParams->nTargetReferenceLevel);
                 mDrcWrap.setParam(DRC_PRES_MODE_WRAP_DESIRED_TARGET,
                         aacPresParams->nTargetReferenceLevel);
                 updateDrcWrapper = true;
+                mDrcTargetRefLevel = aacPresParams->nTargetReferenceLevel;
             }
             if (aacPresParams->nEncodedTargetLevel >= 0) {
                 ALOGV("set nEncodedTargetLevel=%d", aacPresParams->nEncodedTargetLevel);
                 mDrcWrap.setParam(DRC_PRES_MODE_WRAP_ENCODER_TARGET,
                         aacPresParams->nEncodedTargetLevel);
                 updateDrcWrapper = true;
+                mDrcEncTargetLevel = aacPresParams->nEncodedTargetLevel;
             }
             if (aacPresParams->nPCMLimiterEnable >= 0) {
                 aacDecoder_SetParam(mAACDecoder, AAC_PCM_LIMITER_ENABLE,
                         (aacPresParams->nPCMLimiterEnable != 0));
             }
+            if (aacPresParams ->nDrcOutputLoudness != DRC_DEFAULT_MOBILE_OUTPUT_LOUDNESS) {
+                mDrcOutputLoudness = aacPresParams ->nDrcOutputLoudness;
+            }
             if (updateDrcWrapper) {
                 mDrcWrap.update();
             }
@@ -854,6 +895,11 @@
                     // fall through
                 }
 
+                if ( mDrcOutputLoudness != mStreamInfo->outputLoudness) {
+                    ALOGD("update Loudness, before = %d, now = %d", mDrcOutputLoudness, mStreamInfo->outputLoudness);
+                    mDrcOutputLoudness = mStreamInfo->outputLoudness;
+                }
+
                 /*
                  * AAC+/eAAC+ streams can be signalled in two ways: either explicitly
                  * or implicitly, according to MPEG4 spec. AAC+/eAAC+ is a dual
diff --git a/media/libstagefright/codecs/aacdec/SoftAAC2.h b/media/libstagefright/codecs/aacdec/SoftAAC2.h
index 5bee710..9f98aa1 100644
--- a/media/libstagefright/codecs/aacdec/SoftAAC2.h
+++ b/media/libstagefright/codecs/aacdec/SoftAAC2.h
@@ -85,6 +85,17 @@
     int32_t mOutputDelayRingBufferWritePos;
     int32_t mOutputDelayRingBufferReadPos;
     int32_t mOutputDelayRingBufferFilled;
+
+    //drc
+    int32_t mDrcCompressMode;
+    int32_t mDrcTargetRefLevel;
+    int32_t mDrcEncTargetLevel;
+    int32_t mDrcBoostFactor;
+    int32_t mDrcAttenuationFactor;
+    int32_t mDrcEffectType;
+    int32_t mDrcAlbumMode;
+    int32_t mDrcOutputLoudness;
+
     bool outputDelayRingBufferPutSamples(INT_PCM *samples, int numSamples);
     int32_t outputDelayRingBufferGetSamples(INT_PCM *samples, int numSamples);
     int32_t outputDelayRingBufferSamplesAvailable();
diff --git a/media/libstagefright/omx/SimpleSoftOMXComponent.cpp b/media/libstagefright/omx/SimpleSoftOMXComponent.cpp
index ddb459f..44415aa 100644
--- a/media/libstagefright/omx/SimpleSoftOMXComponent.cpp
+++ b/media/libstagefright/omx/SimpleSoftOMXComponent.cpp
@@ -17,6 +17,10 @@
 //#define LOG_NDEBUG 0
 #define LOG_TAG "SimpleSoftOMXComponent"
 #include <utils/Log.h>
+#include <OMX_Core.h>
+#include <OMX_Audio.h>
+#include <OMX_IndexExt.h>
+#include <OMX_AudioExt.h>
 
 #include <media/stagefright/omx/SimpleSoftOMXComponent.h>
 #include <media/stagefright/foundation/ADebug.h>
@@ -74,7 +78,7 @@
 
     OMX_U32 portIndex;
 
-    switch (index) {
+    switch ((int)index) {
         case OMX_IndexParamPortDefinition:
         {
             const OMX_PARAM_PORTDEFINITIONTYPE *portDefs =
@@ -108,6 +112,19 @@
             break;
         }
 
+         case OMX_IndexParamAudioAndroidAacDrcPresentation:
+        {
+            if (mState == OMX_StateInvalid) {
+                return false;
+            }
+            const OMX_AUDIO_PARAM_ANDROID_AACDRCPRESENTATIONTYPE *aacPresParams =
+                            (const OMX_AUDIO_PARAM_ANDROID_AACDRCPRESENTATIONTYPE *)params;
+            if (!isValidOMXParam(aacPresParams)) {
+                return false;
+            }
+            return true;
+         }
+
         default:
             return false;
     }
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 9ba99bc..e7a12df 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -31,6 +31,7 @@
 #include <sys/resource.h>
 #include <thread>
 
+
 #include <android/os/IExternalVibratorService.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
@@ -41,8 +42,10 @@
 #include <media/audiohal/DevicesFactoryHalInterface.h>
 #include <media/audiohal/EffectsFactoryHalInterface.h>
 #include <media/AudioParameter.h>
+#include <media/IAudioPolicyService.h>
 #include <media/MediaMetricsItem.h>
 #include <media/TypeConverter.h>
+#include <mediautils/TimeCheck.h>
 #include <memunreachable/memunreachable.h>
 #include <utils/String16.h>
 #include <utils/threads.h>
@@ -69,6 +72,7 @@
 
 #include <media/IMediaLogService.h>
 #include <media/AidlConversion.h>
+#include <media/AudioValidator.h>
 #include <media/nbaio/Pipe.h>
 #include <media/nbaio/PipeReader.h>
 #include <mediautils/BatteryNotifier.h>
@@ -2335,6 +2339,11 @@
 {
     ALOGV(__func__);
 
+    status_t status = AudioValidator::validateAudioPortConfig(*config);
+    if (status != NO_ERROR) {
+        return status;
+    }
+
     audio_module_handle_t module;
     if (config->type == AUDIO_PORT_TYPE_DEVICE) {
         module = config->ext.device.hw_module;
@@ -4036,6 +4045,106 @@
 status_t AudioFlinger::onTransact(
         uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
 {
+    // make sure transactions reserved to AudioPolicyManager do not come from other processes
+    switch (code) {
+        case SET_STREAM_VOLUME:
+        case SET_STREAM_MUTE:
+        case OPEN_OUTPUT:
+        case OPEN_DUPLICATE_OUTPUT:
+        case CLOSE_OUTPUT:
+        case SUSPEND_OUTPUT:
+        case RESTORE_OUTPUT:
+        case OPEN_INPUT:
+        case CLOSE_INPUT:
+        case INVALIDATE_STREAM:
+        case SET_VOICE_VOLUME:
+        case MOVE_EFFECTS:
+        case SET_EFFECT_SUSPENDED:
+        case LOAD_HW_MODULE:
+        case LIST_AUDIO_PORTS:
+        case GET_AUDIO_PORT:
+        case CREATE_AUDIO_PATCH:
+        case RELEASE_AUDIO_PATCH:
+        case LIST_AUDIO_PATCHES:
+        case SET_AUDIO_PORT_CONFIG:
+        case SET_RECORD_SILENCED:
+            ALOGW("%s: transaction %d received from PID %d",
+                  __func__, code, IPCThreadState::self()->getCallingPid());
+            // return status only for non void methods
+            switch (code) {
+                case SET_RECORD_SILENCED:
+                case SET_EFFECT_SUSPENDED:
+                    break;
+                default:
+                    reply->writeInt32(static_cast<int32_t> (INVALID_OPERATION));
+                    break;
+            }
+            return OK;
+        default:
+            break;
+    }
+
+    // make sure the following transactions come from system components
+    switch (code) {
+        case SET_MASTER_VOLUME:
+        case SET_MASTER_MUTE:
+        case SET_MODE:
+        case SET_MIC_MUTE:
+        case SET_LOW_RAM_DEVICE:
+        case SYSTEM_READY:
+        case SET_AUDIO_HAL_PIDS: {
+            if (!isServiceUid(IPCThreadState::self()->getCallingUid())) {
+                ALOGW("%s: transaction %d received from PID %d unauthorized UID %d",
+                      __func__, code, IPCThreadState::self()->getCallingPid(),
+                      IPCThreadState::self()->getCallingUid());
+                // return status only for non void methods
+                switch (code) {
+                    case SYSTEM_READY:
+                        break;
+                    default:
+                        reply->writeInt32(static_cast<int32_t> (INVALID_OPERATION));
+                        break;
+                }
+                return OK;
+            }
+        } break;
+        default:
+            break;
+    }
+
+    // List of relevant events that trigger log merging.
+    // Log merging should activate during audio activity of any kind. This are considered the
+    // most relevant events.
+    // TODO should select more wisely the items from the list
+    switch (code) {
+        case CREATE_TRACK:
+        case CREATE_RECORD:
+        case SET_MASTER_VOLUME:
+        case SET_MASTER_MUTE:
+        case SET_MIC_MUTE:
+        case SET_PARAMETERS:
+        case CREATE_EFFECT:
+        case SYSTEM_READY: {
+            requestLogMerge();
+            break;
+        }
+        default:
+            break;
+    }
+
+    std::string tag("IAudioFlinger command " + std::to_string(code));
+    TimeCheck check(tag.c_str());
+
+    // Make sure we connect to Audio Policy Service before calling into AudioFlinger:
+    //  - AudioFlinger can call into Audio Policy Service with its global mutex held
+    //  - If this is the first time Audio Policy Service is queried from inside audioserver process
+    //  this will trigger Audio Policy Manager initialization.
+    //  - Audio Policy Manager initialization calls into AudioFlinger which will try to lock
+    //  its global mutex and a deadlock will occur.
+    if (IPCThreadState::self()->getCallingPid() != getpid()) {
+        AudioSystem::get_audio_policy_service();
+    }
+
     return BnAudioFlinger::onTransact(code, data, reply, flags);
 }
 
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 6dfc48f..a2e50f8 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -519,6 +519,7 @@
     const sp<MediaLogNotifier> mMediaLogNotifier;
 
     // This is a helper that is called during incoming binder calls.
+    // Requests media.log to start merging log buffers
     void requestLogMerge();
 
     class TrackHandle;
diff --git a/services/audioflinger/PatchPanel.cpp b/services/audioflinger/PatchPanel.cpp
index b956b96..1e11660 100644
--- a/services/audioflinger/PatchPanel.cpp
+++ b/services/audioflinger/PatchPanel.cpp
@@ -25,6 +25,7 @@
 
 #include "AudioFlinger.h"
 #include <media/AudioParameter.h>
+#include <media/AudioValidator.h>
 #include <media/DeviceDescriptorBase.h>
 #include <media/PatchBuilder.h>
 #include <mediautils/ServiceUtilities.h>
@@ -56,6 +57,11 @@
 
 /* Get supported attributes for a given audio port */
 status_t AudioFlinger::getAudioPort(struct audio_port_v7 *port) {
+    status_t status = AudioValidator::validateAudioPort(*port);
+    if (status != NO_ERROR) {
+        return status;
+    }
+
     Mutex::Autolock _l(mLock);
     return mPatchPanel.getAudioPort(port);
 }
@@ -64,6 +70,11 @@
 status_t AudioFlinger::createAudioPatch(const struct audio_patch *patch,
                                    audio_patch_handle_t *handle)
 {
+    status_t status = AudioValidator::validateAudioPatch(*patch);
+    if (status != NO_ERROR) {
+        return status;
+    }
+
     Mutex::Autolock _l(mLock);
     return mPatchPanel.createAudioPatch(patch, handle);
 }
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
index 1d9223e..1302486 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioOutputDescriptor.h
@@ -182,6 +182,7 @@
      * Active ref count of the client will be incremented/decremented through setActive API
      */
     virtual void setClientActive(const sp<TrackClientDescriptor>& client, bool active);
+    bool isClientActive(const sp<TrackClientDescriptor>& client);
 
     bool isActive(uint32_t inPastMs) const;
     bool isActive(VolumeSource volumeSource = VOLUME_SOURCE_NONE,
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
index 25f7c27..1756021 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioOutputDescriptor.cpp
@@ -123,6 +123,12 @@
     client->setActive(active);
 }
 
+bool AudioOutputDescriptor::isClientActive(const sp<TrackClientDescriptor>& client)
+{
+    return client != nullptr &&
+            std::find(begin(mActiveClients), end(mActiveClients), client) != end(mActiveClients);
+}
+
 bool AudioOutputDescriptor::isActive(VolumeSource vs, uint32_t inPastMs, nsecs_t sysTime) const
 {
     return (vs == VOLUME_SOURCE_NONE) ?
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 7b8a2ea..cc4ec36 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -1954,6 +1954,12 @@
 
     ALOGV("releaseOutput() %d", outputDesc->mIoHandle);
 
+    sp<TrackClientDescriptor> client = outputDesc->getClient(portId);
+    if (outputDesc->isClientActive(client)) {
+        ALOGW("releaseOutput() inactivates portId %d in good faith", portId);
+        stopOutput(portId);
+    }
+
     if (outputDesc->mFlags & AUDIO_OUTPUT_FLAG_DIRECT) {
         if (outputDesc->mDirectOpenCount <= 0) {
             ALOGW("releaseOutput() invalid open count %d for output %d",
@@ -1965,9 +1971,7 @@
             mpClientInterface->onAudioPortListUpdate();
         }
     }
-    // stopOutput() needs to be successfully called before releaseOutput()
-    // otherwise there may be inaccurate stream reference counts.
-    // This is checked in outputDesc->removeClient below.
+
     outputDesc->removeClient(portId);
 }