audio flinger: add patch connection between hw modules

Add support for audio device connections between different audio
hw modules.
The patch is performed by creating a bridge between the playback
thread connected to the sink device and the record thread connected
to the source device using a pair of specialized PlaybackTrack and
RecordTrack.
- Added PatchTrack and PatchRecord classes.
- Added TrackBase type to indicate more clearly the track behavior.
- A TrackBase can allocate the buffer or reuse an existing one.
- Factored some code in openOutput() and openInput() for internal use
by PatchPanel.

Bug: 14815883.

Change-Id: Ib9515fcda864610458a4bc81fa8f59096ff4d7db
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 1ad6285..8bf709e 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -1531,7 +1531,7 @@
     }
 
     audio_module_handle_t handle = nextUniqueId();
-    mAudioHwDevs.add(handle, new AudioHwDevice(name, dev, flags));
+    mAudioHwDevs.add(handle, new AudioHwDevice(handle, name, dev, flags));
 
     ALOGI("loadHwModule() Loaded %s audio interface from %s (%s) handle %d",
           name, dev->common.module->name, dev->common.module->id, handle);
@@ -1575,6 +1575,75 @@
 
 // ----------------------------------------------------------------------------
 
+
+sp<AudioFlinger::PlaybackThread> AudioFlinger::openOutput_l(audio_module_handle_t module,
+                                                            audio_devices_t device,
+                                                            struct audio_config *config,
+                                                            audio_output_flags_t flags)
+{
+    AudioHwDevice *outHwDev = findSuitableHwDev_l(module, device);
+    if (outHwDev == NULL) {
+        return AUDIO_IO_HANDLE_NONE;
+    }
+
+    audio_hw_device_t *hwDevHal = outHwDev->hwDevice();
+    audio_io_handle_t id = nextUniqueId();
+
+    mHardwareStatus = AUDIO_HW_OUTPUT_OPEN;
+
+    audio_stream_out_t *outStream = NULL;
+
+    // FOR TESTING ONLY:
+    // Enable increased sink precision for mixing mode if kEnableExtendedPrecision is true.
+    if (kEnableExtendedPrecision &&  // Check only for Normal Mixing mode
+            !(flags & (AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD | AUDIO_OUTPUT_FLAG_DIRECT))) {
+        // Update format
+        //config.format = AUDIO_FORMAT_PCM_FLOAT;
+        //config.format = AUDIO_FORMAT_PCM_24_BIT_PACKED;
+        //config.format = AUDIO_FORMAT_PCM_32_BIT;
+        //config.format = AUDIO_FORMAT_PCM_8_24_BIT;
+        // ALOGV("openOutput() upgrading format to %#08x", config.format);
+    }
+
+    status_t status = hwDevHal->open_output_stream(hwDevHal,
+                                          id,
+                                          device,
+                                          flags,
+                                          config,
+                                          &outStream);
+
+    mHardwareStatus = AUDIO_HW_IDLE;
+    ALOGV("openOutput_l() openOutputStream returned output %p, SamplingRate %d, Format %#08x, "
+            "Channels %x, status %d",
+            outStream,
+            config->sample_rate,
+            config->format,
+            config->channel_mask,
+            status);
+
+    if (status == NO_ERROR && outStream != NULL) {
+        AudioStreamOut *output = new AudioStreamOut(outHwDev, outStream, flags);
+
+        PlaybackThread *thread;
+        if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
+            thread = new OffloadThread(this, output, id, device);
+            ALOGV("openOutput() created offload output: ID %d thread %p", id, thread);
+        } else if ((flags & AUDIO_OUTPUT_FLAG_DIRECT)
+                || !isValidPcmSinkFormat(config->format)
+                || (config->channel_mask != AUDIO_CHANNEL_OUT_STEREO)) {
+            thread = new DirectOutputThread(this, output, id, device);
+            ALOGV("openOutput() created direct output: ID %d thread %p", id, thread);
+        } else {
+            thread = new MixerThread(this, output, id, device);
+            ALOGV("openOutput() created mixer output: ID %d thread %p", id, thread);
+        }
+        mPlaybackThreads.add(id, thread);
+        return thread;
+    }
+
+    return 0;
+}
+
 audio_io_handle_t AudioFlinger::openOutput(audio_module_handle_t module,
                                            audio_devices_t *pDevices,
                                            uint32_t *pSamplingRate,
@@ -1609,64 +1678,8 @@
 
     Mutex::Autolock _l(mLock);
 
-    AudioHwDevice *outHwDev = findSuitableHwDev_l(module, *pDevices);
-    if (outHwDev == NULL) {
-        return AUDIO_IO_HANDLE_NONE;
-    }
-
-    audio_hw_device_t *hwDevHal = outHwDev->hwDevice();
-    audio_io_handle_t id = nextUniqueId();
-
-    mHardwareStatus = AUDIO_HW_OUTPUT_OPEN;
-
-    audio_stream_out_t *outStream = NULL;
-
-    // FOR TESTING ONLY:
-    // Enable increased sink precision for mixing mode if kEnableExtendedPrecision is true.
-    if (kEnableExtendedPrecision &&  // Check only for Normal Mixing mode
-            !(flags & (AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD | AUDIO_OUTPUT_FLAG_DIRECT))) {
-        // Update format
-        //config.format = AUDIO_FORMAT_PCM_FLOAT;
-        //config.format = AUDIO_FORMAT_PCM_24_BIT_PACKED;
-        //config.format = AUDIO_FORMAT_PCM_32_BIT;
-        //config.format = AUDIO_FORMAT_PCM_8_24_BIT;
-        // ALOGV("openOutput() upgrading format to %#08x", config.format);
-    }
-
-    status_t status = hwDevHal->open_output_stream(hwDevHal,
-                                          id,
-                                          *pDevices,
-                                          (audio_output_flags_t)flags,
-                                          &config,
-                                          &outStream);
-
-    mHardwareStatus = AUDIO_HW_IDLE;
-    ALOGV("openOutput() openOutputStream returned output %p, SamplingRate %d, Format %#08x, "
-            "Channels %x, status %d",
-            outStream,
-            config.sample_rate,
-            config.format,
-            config.channel_mask,
-            status);
-
-    if (status == NO_ERROR && outStream != NULL) {
-        AudioStreamOut *output = new AudioStreamOut(outHwDev, outStream, flags);
-
-        PlaybackThread *thread;
-        if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
-            thread = new OffloadThread(this, output, id, *pDevices);
-            ALOGV("openOutput() created offload output: ID %d thread %p", id, thread);
-        } else if ((flags & AUDIO_OUTPUT_FLAG_DIRECT)
-                || !isValidPcmSinkFormat(config.format)
-                || (config.channel_mask != AUDIO_CHANNEL_OUT_STEREO)) {
-            thread = new DirectOutputThread(this, output, id, *pDevices);
-            ALOGV("openOutput() created direct output: ID %d thread %p", id, thread);
-        } else {
-            thread = new MixerThread(this, output, id, *pDevices);
-            ALOGV("openOutput() created mixer output: ID %d thread %p", id, thread);
-        }
-        mPlaybackThreads.add(id, thread);
-
+    sp<PlaybackThread> thread = openOutput_l(module, *pDevices, &config, flags);
+    if (thread != 0) {
         if (pSamplingRate != NULL) {
             *pSamplingRate = config.sample_rate;
         }
@@ -1686,16 +1699,16 @@
         // the first primary output opened designates the primary hw device
         if ((mPrimaryHardwareDev == NULL) && (flags & AUDIO_OUTPUT_FLAG_PRIMARY)) {
             ALOGI("Using module %d has the primary audio interface", module);
-            mPrimaryHardwareDev = outHwDev;
+            mPrimaryHardwareDev = thread->getOutput()->audioHwDev;
 
             AutoMutex lock(mHardwareLock);
             mHardwareStatus = AUDIO_HW_SET_MODE;
-            hwDevHal->set_mode(hwDevHal, mMode);
+            mPrimaryHardwareDev->hwDevice()->set_mode(mPrimaryHardwareDev->hwDevice(), mMode);
             mHardwareStatus = AUDIO_HW_IDLE;
 
             mPrimaryOutputSampleRate = config.sample_rate;
         }
-        return id;
+        return thread->id();
     }
 
     return AUDIO_IO_HANDLE_NONE;
@@ -1776,15 +1789,29 @@
     // but the ThreadBase container still exists.
 
     if (thread->type() != ThreadBase::DUPLICATING) {
-        AudioStreamOut *out = thread->clearOutput();
-        ALOG_ASSERT(out != NULL, "out shouldn't be NULL");
-        // from now on thread->mOutput is NULL
-        out->hwDev()->close_output_stream(out->hwDev(), out->stream);
-        delete out;
+        closeOutputFinish(thread);
     }
+
+    thread.clear();
     return NO_ERROR;
 }
 
+void AudioFlinger::closeOutputFinish(sp<PlaybackThread> thread)
+{
+    AudioStreamOut *out = thread->clearOutput();
+    ALOG_ASSERT(out != NULL, "out shouldn't be NULL");
+    // from now on thread->mOutput is NULL
+    out->hwDev()->close_output_stream(out->hwDev(), out->stream);
+    delete out;
+}
+
+void AudioFlinger::closeOutputInternal_l(sp<PlaybackThread> thread)
+{
+    mPlaybackThreads.removeItem(thread->mId);
+    thread->exit();
+    closeOutputFinish(thread);
+}
+
 status_t AudioFlinger::suspendOutput(audio_io_handle_t output)
 {
     Mutex::Autolock _l(mLock);
@@ -1823,6 +1850,12 @@
                                           audio_channel_mask_t *pChannelMask,
                                           audio_input_flags_t flags)
 {
+    Mutex::Autolock _l(mLock);
+
+    if (pDevices == NULL || *pDevices == AUDIO_DEVICE_NONE) {
+        return AUDIO_IO_HANDLE_NONE;
+    }
+
     struct audio_config config;
     memset(&config, 0, sizeof(config));
     config.sample_rate = (pSamplingRate != NULL) ? *pSamplingRate : 0;
@@ -1833,13 +1866,36 @@
     audio_format_t reqFormat = config.format;
     audio_channel_mask_t reqChannelMask = config.channel_mask;
 
-    if (pDevices == NULL || *pDevices == AUDIO_DEVICE_NONE) {
-        return 0;
+    sp<RecordThread> thread = openInput_l(module, *pDevices, &config, flags);
+
+    if (thread != 0) {
+        if (pSamplingRate != NULL) {
+            *pSamplingRate = reqSamplingRate;
+        }
+        if (pFormat != NULL) {
+            *pFormat = config.format;
+        }
+        if (pChannelMask != NULL) {
+            *pChannelMask = reqChannelMask;
+        }
+
+        // notify client processes of the new input creation
+        thread->audioConfigChanged(AudioSystem::INPUT_OPENED);
+        return thread->id();
     }
+    return AUDIO_IO_HANDLE_NONE;
+}
 
-    Mutex::Autolock _l(mLock);
+sp<AudioFlinger::RecordThread> AudioFlinger::openInput_l(audio_module_handle_t module,
+                                                         audio_devices_t device,
+                                                         struct audio_config *config,
+                                                         audio_input_flags_t flags)
+{
+    uint32_t reqSamplingRate = config->sample_rate;
+    audio_format_t reqFormat = config->format;
+    audio_channel_mask_t reqChannelMask = config->channel_mask;
 
-    AudioHwDevice *inHwDev = findSuitableHwDev_l(module, *pDevices);
+    AudioHwDevice *inHwDev = findSuitableHwDev_l(module, device);
     if (inHwDev == NULL) {
         return 0;
     }
@@ -1848,14 +1904,14 @@
     audio_io_handle_t id = nextUniqueId();
 
     audio_stream_in_t *inStream = NULL;
-    status_t status = inHwHal->open_input_stream(inHwHal, id, *pDevices, &config,
+    status_t status = inHwHal->open_input_stream(inHwHal, id, device, config,
                                         &inStream, flags);
     ALOGV("openInput() openInputStream returned input %p, SamplingRate %d, Format %#x, Channels %x, "
             "flags %#x, status %d",
             inStream,
-            config.sample_rate,
-            config.format,
-            config.channel_mask,
+            config->sample_rate,
+            config->format,
+            config->channel_mask,
             flags,
             status);
 
@@ -1863,14 +1919,14 @@
     // conversion internally, try to open again with the proposed parameters. The AudioFlinger can
     // resample the input and do mono to stereo or stereo to mono conversions on 16 bit PCM inputs.
     if (status == BAD_VALUE &&
-        reqFormat == config.format && config.format == AUDIO_FORMAT_PCM_16_BIT &&
-        (config.sample_rate <= 2 * reqSamplingRate) &&
-        (audio_channel_count_from_in_mask(config.channel_mask) <= FCC_2) &&
+        reqFormat == config->format && config->format == AUDIO_FORMAT_PCM_16_BIT &&
+        (config->sample_rate <= 2 * reqSamplingRate) &&
+        (audio_channel_count_from_in_mask(config->channel_mask) <= FCC_2) &&
         (audio_channel_count_from_in_mask(reqChannelMask) <= FCC_2)) {
         // FIXME describe the change proposed by HAL (save old values so we can log them here)
         ALOGV("openInput() reopening with proposed sampling rate and channel mask");
         inStream = NULL;
-        status = inHwHal->open_input_stream(inHwHal, id, *pDevices, &config, &inStream, flags);
+        status = inHwHal->open_input_stream(inHwHal, id, device, config, &inStream, flags);
         // FIXME log this new status; HAL should not propose any further changes
     }
 
@@ -1931,30 +1987,18 @@
         // Start record thread
         // RecordThread requires both input and output device indication to forward to audio
         // pre processing modules
-        RecordThread *thread = new RecordThread(this,
+        sp<RecordThread> thread = new RecordThread(this,
                                   input,
                                   id,
                                   primaryOutputDevice_l(),
-                                  *pDevices
+                                  device
 #ifdef TEE_SINK
                                   , teeSink
 #endif
                                   );
         mRecordThreads.add(id, thread);
-        ALOGV("openInput() created record thread: ID %d thread %p", id, thread);
-        if (pSamplingRate != NULL) {
-            *pSamplingRate = reqSamplingRate;
-        }
-        if (pFormat != NULL) {
-            *pFormat = config.format;
-        }
-        if (pChannelMask != NULL) {
-            *pChannelMask = reqChannelMask;
-        }
-
-        // notify client processes of the new input creation
-        thread->audioConfigChanged(AudioSystem::INPUT_OPENED);
-        return id;
+        ALOGV("openInput() created record thread: ID %d thread %p", id, thread.get());
+        return thread;
     }
 
     return 0;
@@ -1981,17 +2025,26 @@
         audioConfigChanged(AudioSystem::INPUT_CLOSED, input, NULL);
         mRecordThreads.removeItem(input);
     }
-    thread->exit();
-    // The thread entity (active unit of execution) is no longer running here,
-    // but the ThreadBase container still exists.
+    // FIXME: calling thread->exit() without mLock held should not be needed anymore now that
+    // we have a different lock for notification client
+    closeInputFinish(thread);
+    return NO_ERROR;
+}
 
+void AudioFlinger::closeInputFinish(sp<RecordThread> thread)
+{
+    thread->exit();
     AudioStreamIn *in = thread->clearInput();
     ALOG_ASSERT(in != NULL, "in shouldn't be NULL");
     // from now on thread->mInput is NULL
     in->hwDev()->close_input_stream(in->hwDev(), in->stream);
     delete in;
+}
 
-    return NO_ERROR;
+void AudioFlinger::closeInputInternal_l(sp<RecordThread> thread)
+{
+    mRecordThreads.removeItem(thread->mId);
+    closeInputFinish(thread);
 }
 
 status_t AudioFlinger::invalidateStream(audio_stream_type_t stream)
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index bae18fd..2830e6d 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -489,6 +489,18 @@
               PlaybackThread *checkPlaybackThread_l(audio_io_handle_t output) const;
               MixerThread *checkMixerThread_l(audio_io_handle_t output) const;
               RecordThread *checkRecordThread_l(audio_io_handle_t input) const;
+              sp<RecordThread> openInput_l(audio_module_handle_t module,
+                                           audio_devices_t device,
+                                           struct audio_config *config,
+                                           audio_input_flags_t flags);
+              sp<PlaybackThread> openOutput_l(audio_module_handle_t module,
+                                              audio_devices_t device,
+                                              struct audio_config *config,
+                                              audio_output_flags_t flags);
+
+              void closeOutputFinish(sp<PlaybackThread> thread);
+              void closeInputFinish(sp<RecordThread> thread);
+
               // no range check, AudioFlinger::mLock held
               bool streamMute_l(audio_stream_type_t stream) const
                                 { return mStreamTypes[stream].mute; }
@@ -530,10 +542,11 @@
             AHWD_CAN_SET_MASTER_MUTE    = 0x2,
         };
 
-        AudioHwDevice(const char *moduleName,
+        AudioHwDevice(audio_module_handle_t handle,
+                      const char *moduleName,
                       audio_hw_device_t *hwDevice,
                       Flags flags)
-            : mModuleName(strdup(moduleName))
+            : mHandle(handle), mModuleName(strdup(moduleName))
             , mHwDevice(hwDevice)
             , mFlags(flags) { }
         /*virtual*/ ~AudioHwDevice() { free((void *)mModuleName); }
@@ -546,11 +559,13 @@
             return (0 != (mFlags & AHWD_CAN_SET_MASTER_MUTE));
         }
 
+        audio_module_handle_t handle() const { return mHandle; }
         const char *moduleName() const { return mModuleName; }
         audio_hw_device_t *hwDevice() const { return mHwDevice; }
         uint32_t version() const { return mHwDevice->common.version; }
 
     private:
+        audio_module_handle_t mHandle;
         const char * const mModuleName;
         audio_hw_device_t * const mHwDevice;
         const Flags mFlags;
@@ -669,7 +684,9 @@
 
     // for use from destructor
     status_t    closeOutput_nonvirtual(audio_io_handle_t output);
+    void        closeOutputInternal_l(sp<PlaybackThread> thread);
     status_t    closeInput_nonvirtual(audio_io_handle_t input);
+    void        closeInputInternal_l(sp<RecordThread> thread);
 
 #ifdef TEE_SINK
     // all record threads serially share a common tee sink, which is re-created on format change
diff --git a/services/audioflinger/PatchPanel.cpp b/services/audioflinger/PatchPanel.cpp
index 6d84296..bf509e7 100644
--- a/services/audioflinger/PatchPanel.cpp
+++ b/services/audioflinger/PatchPanel.cpp
@@ -142,102 +142,172 @@
     ALOGV("createAudioPatch() num_sources %d num_sinks %d handle %d",
           patch->num_sources, patch->num_sinks, *handle);
     status_t status = NO_ERROR;
-
     audio_patch_handle_t halHandle = AUDIO_PATCH_HANDLE_NONE;
-
     sp<AudioFlinger> audioflinger = mAudioFlinger.promote();
     if (audioflinger == 0) {
         return NO_INIT;
     }
+
     if (handle == NULL || patch == NULL) {
         return BAD_VALUE;
     }
-    // limit number of sources to 1 for now
-    if (patch->num_sources == 0 || patch->num_sources > 1 ||
+    // limit number of sources to 1 for now or 2 sources for special cross hw module case.
+    // only the audio policy manager can request a patch creation with 2 sources.
+    if (patch->num_sources == 0 || patch->num_sources > 2 ||
             patch->num_sinks == 0 || patch->num_sinks > AUDIO_PATCH_PORTS_MAX) {
         return BAD_VALUE;
     }
 
-    for (size_t index = 0; *handle != 0 && index < mPatches.size(); index++) {
-        if (*handle == mPatches[index]->mHandle) {
-            ALOGV("createAudioPatch() removing patch handle %d", *handle);
-            halHandle = mPatches[index]->mHalHandle;
-            mPatches.removeAt(index);
-            break;
+    if (*handle != AUDIO_PATCH_HANDLE_NONE) {
+        for (size_t index = 0; *handle != 0 && index < mPatches.size(); index++) {
+            if (*handle == mPatches[index]->mHandle) {
+                ALOGV("createAudioPatch() removing patch handle %d", *handle);
+                halHandle = mPatches[index]->mHalHandle;
+                mPatches.removeAt(index);
+                break;
+            }
         }
     }
 
+    Patch *newPatch = new Patch(patch);
+
     switch (patch->sources[0].type) {
         case AUDIO_PORT_TYPE_DEVICE: {
             // limit number of sinks to 1 for now
             if (patch->num_sinks > 1) {
-                return BAD_VALUE;
+                status = BAD_VALUE;
+                goto exit;
             }
             audio_module_handle_t src_module = patch->sources[0].ext.device.hw_module;
             ssize_t index = audioflinger->mAudioHwDevs.indexOfKey(src_module);
             if (index < 0) {
                 ALOGW("createAudioPatch() bad src hw module %d", src_module);
-                return BAD_VALUE;
+                status = BAD_VALUE;
+                goto exit;
             }
             AudioHwDevice *audioHwDevice = audioflinger->mAudioHwDevs.valueAt(index);
             for (unsigned int i = 0; i < patch->num_sinks; i++) {
                 // reject connection to different sink types
                 if (patch->sinks[i].type != patch->sinks[0].type) {
                     ALOGW("createAudioPatch() different sink types in same patch not supported");
-                    return BAD_VALUE;
+                    status = BAD_VALUE;
+                    goto exit;
                 }
-                // limit to connections between sinks and sources on same HW module
-                if (patch->sinks[i].ext.mix.hw_module != src_module) {
-                    ALOGW("createAudioPatch() cannot connect source on module %d to "
-                            "sink on module %d", src_module, patch->sinks[i].ext.mix.hw_module);
-                    return BAD_VALUE;
-                }
-
-                // limit to connections between devices and output streams for HAL before 3.0
-                if ((audioHwDevice->version() < AUDIO_DEVICE_API_VERSION_3_0) &&
+                // limit to connections between devices and input streams for HAL before 3.0
+                if (patch->sinks[i].ext.mix.hw_module == src_module &&
+                        (audioHwDevice->version() < AUDIO_DEVICE_API_VERSION_3_0) &&
                         (patch->sinks[i].type != AUDIO_PORT_TYPE_MIX)) {
                     ALOGW("createAudioPatch() invalid sink type %d for device source",
                           patch->sinks[i].type);
-                    return BAD_VALUE;
+                    status = BAD_VALUE;
+                    goto exit;
                 }
             }
 
-            if (audioHwDevice->version() >= AUDIO_DEVICE_API_VERSION_3_0) {
-                if (patch->sinks[0].type == AUDIO_PORT_TYPE_MIX) {
-                    sp<ThreadBase> thread = audioflinger->checkRecordThread_l(
-                                                                    patch->sinks[0].ext.mix.handle);
-                    if (thread == 0) {
-                        ALOGW("createAudioPatch() bad capture I/O handle %d",
-                                                                  patch->sinks[0].ext.mix.handle);
-                        return BAD_VALUE;
+            if (patch->sinks[0].ext.device.hw_module != src_module) {
+                // limit to device to device connection if not on same hw module
+                if (patch->sinks[0].type != AUDIO_PORT_TYPE_DEVICE) {
+                    ALOGW("createAudioPatch() invalid sink type for cross hw module");
+                    status = INVALID_OPERATION;
+                    goto exit;
+                }
+                // special case num sources == 2 -=> reuse an exiting output mix to connect to the
+                // sink
+                if (patch->num_sources == 2) {
+                    if (patch->sources[1].type != AUDIO_PORT_TYPE_MIX ||
+                            patch->sinks[0].ext.device.hw_module !=
+                                    patch->sources[1].ext.mix.hw_module) {
+                        ALOGW("createAudioPatch() invalid source combination");
+                        status = INVALID_OPERATION;
+                        goto exit;
                     }
-                    status = thread->sendCreateAudioPatchConfigEvent(patch, &halHandle);
+
+                    sp<ThreadBase> thread =
+                            audioflinger->checkPlaybackThread_l(patch->sources[1].ext.mix.handle);
+                    newPatch->mPlaybackThread = (MixerThread *)thread.get();
+                    if (thread == 0) {
+                        ALOGW("createAudioPatch() cannot get playback thread");
+                        status = INVALID_OPERATION;
+                        goto exit;
+                    }
                 } else {
-                    audio_hw_device_t *hwDevice = audioHwDevice->hwDevice();
-                    status = hwDevice->create_audio_patch(hwDevice,
-                                                           patch->num_sources,
-                                                           patch->sources,
-                                                           patch->num_sinks,
-                                                           patch->sinks,
-                                                           &halHandle);
+                    struct audio_config config;
+                    config.sample_rate = 0;
+                    config.channel_mask = AUDIO_CHANNEL_NONE;
+                    config.format = AUDIO_FORMAT_DEFAULT;
+                    newPatch->mPlaybackThread = audioflinger->openOutput_l(
+                                                             patch->sinks[0].ext.device.hw_module,
+                                                             patch->sinks[0].ext.device.type,
+                                                             &config,
+                                                             AUDIO_OUTPUT_FLAG_NONE);
+                    ALOGV("audioflinger->openOutput_l() returned %p",
+                                          newPatch->mPlaybackThread.get());
+                    if (newPatch->mPlaybackThread == 0) {
+                        status = NO_MEMORY;
+                        goto exit;
+                    }
+                }
+                uint32_t channelCount = newPatch->mPlaybackThread->channelCount();
+                audio_devices_t device = patch->sources[0].ext.device.type;
+                struct audio_config config;
+                audio_channel_mask_t inChannelMask = audio_channel_in_mask_from_count(channelCount);
+                config.sample_rate = newPatch->mPlaybackThread->sampleRate();
+                config.channel_mask = inChannelMask;
+                config.format = newPatch->mPlaybackThread->format();
+                newPatch->mRecordThread = audioflinger->openInput_l(src_module,
+                                                                    device,
+                                                                    &config,
+                                                                    AUDIO_INPUT_FLAG_NONE);
+                ALOGV("audioflinger->openInput_l() returned %p inChannelMask %08x",
+                      newPatch->mRecordThread.get(), inChannelMask);
+                if (newPatch->mRecordThread == 0) {
+                    status = NO_MEMORY;
+                    goto exit;
+                }
+                status = createPatchConnections(newPatch, patch);
+                if (status != NO_ERROR) {
+                    goto exit;
                 }
             } else {
-                sp<ThreadBase> thread = audioflinger->checkRecordThread_l(
-                                                                    patch->sinks[0].ext.mix.handle);
-                if (thread == 0) {
-                    ALOGW("createAudioPatch() bad capture I/O handle %d",
-                                                                  patch->sinks[0].ext.mix.handle);
-                    return BAD_VALUE;
-                }
-                AudioParameter param;
-                param.addInt(String8(AudioParameter::keyRouting),
-                             (int)patch->sources[0].ext.device.type);
-                param.addInt(String8(AudioParameter::keyInputSource),
-                                                     (int)patch->sinks[0].ext.mix.usecase.source);
+                if (audioHwDevice->version() >= AUDIO_DEVICE_API_VERSION_3_0) {
+                    if (patch->sinks[0].type == AUDIO_PORT_TYPE_MIX) {
+                        sp<ThreadBase> thread = audioflinger->checkRecordThread_l(
+                                                                        patch->sinks[0].ext.mix.handle);
+                        if (thread == 0) {
+                            ALOGW("createAudioPatch() bad capture I/O handle %d",
+                                                                      patch->sinks[0].ext.mix.handle);
+                            status = BAD_VALUE;
+                            goto exit;
+                        }
+                        status = thread->sendCreateAudioPatchConfigEvent(patch, &halHandle);
+                    } else {
+                        audio_hw_device_t *hwDevice = audioHwDevice->hwDevice();
+                        status = hwDevice->create_audio_patch(hwDevice,
+                                                               patch->num_sources,
+                                                               patch->sources,
+                                                               patch->num_sinks,
+                                                               patch->sinks,
+                                                               &halHandle);
+                    }
+                } else {
+                    sp<ThreadBase> thread = audioflinger->checkRecordThread_l(
+                                                                        patch->sinks[0].ext.mix.handle);
+                    if (thread == 0) {
+                        ALOGW("createAudioPatch() bad capture I/O handle %d",
+                                                                      patch->sinks[0].ext.mix.handle);
+                        status = BAD_VALUE;
+                        goto exit;
+                    }
+                    AudioParameter param;
+                    param.addInt(String8(AudioParameter::keyRouting),
+                                 (int)patch->sources[0].ext.device.type);
+                    param.addInt(String8(AudioParameter::keyInputSource),
+                                                         (int)patch->sinks[0].ext.mix.usecase.source);
 
-                ALOGV("createAudioPatch() AUDIO_PORT_TYPE_DEVICE setParameters %s",
-                                                                      param.toString().string());
-                status = thread->setParameters(param.toString());
+                    ALOGV("createAudioPatch() AUDIO_PORT_TYPE_DEVICE setParameters %s",
+                                                                          param.toString().string());
+                    status = thread->setParameters(param.toString());
+                }
             }
         } break;
         case AUDIO_PORT_TYPE_MIX: {
@@ -245,18 +315,21 @@
             ssize_t index = audioflinger->mAudioHwDevs.indexOfKey(src_module);
             if (index < 0) {
                 ALOGW("createAudioPatch() bad src hw module %d", src_module);
-                return BAD_VALUE;
+                status = BAD_VALUE;
+                goto exit;
             }
             // limit to connections between devices and output streams
             for (unsigned int i = 0; i < patch->num_sinks; i++) {
                 if (patch->sinks[i].type != AUDIO_PORT_TYPE_DEVICE) {
-                    ALOGW("createAudioPatch() invalid sink type %d for bus source",
+                    ALOGW("createAudioPatch() invalid sink type %d for mix source",
                           patch->sinks[i].type);
-                    return BAD_VALUE;
+                    status = BAD_VALUE;
+                    goto exit;
                 }
                 // limit to connections between sinks and sources on same HW module
                 if (patch->sinks[i].ext.device.hw_module != src_module) {
-                    return BAD_VALUE;
+                    status = BAD_VALUE;
+                    goto exit;
                 }
             }
             AudioHwDevice *audioHwDevice = audioflinger->mAudioHwDevs.valueAt(index);
@@ -265,7 +338,8 @@
             if (thread == 0) {
                 ALOGW("createAudioPatch() bad playback I/O handle %d",
                           patch->sources[0].ext.mix.handle);
-                return BAD_VALUE;
+                status = BAD_VALUE;
+                goto exit;
             }
             if (audioHwDevice->version() >= AUDIO_DEVICE_API_VERSION_3_0) {
                 status = thread->sendCreateAudioPatchConfigEvent(patch, &halHandle);
@@ -281,20 +355,162 @@
 
         } break;
         default:
-            return BAD_VALUE;
+            status = BAD_VALUE;
+            goto exit;
     }
+exit:
     ALOGV("createAudioPatch() status %d", status);
     if (status == NO_ERROR) {
         *handle = audioflinger->nextUniqueId();
-        Patch *newPatch = new Patch(patch);
         newPatch->mHandle = *handle;
         newPatch->mHalHandle = halHandle;
         mPatches.add(newPatch);
         ALOGV("createAudioPatch() added new patch handle %d halHandle %d", *handle, halHandle);
+    } else {
+        clearPatchConnections(newPatch);
+        delete newPatch;
     }
     return status;
 }
 
+status_t AudioFlinger::PatchPanel::createPatchConnections(Patch *patch,
+                                                          const struct audio_patch *audioPatch)
+{
+    // create patch from source device to record thread input
+    struct audio_patch subPatch;
+    subPatch.num_sources = 1;
+    subPatch.sources[0] = audioPatch->sources[0];
+    subPatch.num_sinks = 1;
+
+    patch->mRecordThread->getAudioPortConfig(&subPatch.sinks[0]);
+    subPatch.sinks[0].ext.mix.usecase.source = AUDIO_SOURCE_MIC;
+
+    status_t status = createAudioPatch(&subPatch, &patch->mRecordPatchHandle);
+    if (status != NO_ERROR) {
+        patch->mRecordPatchHandle = AUDIO_PATCH_HANDLE_NONE;
+        return status;
+    }
+
+    // create patch from playback thread output to sink device
+    patch->mPlaybackThread->getAudioPortConfig(&subPatch.sources[0]);
+    subPatch.sinks[0] = audioPatch->sinks[0];
+    status = createAudioPatch(&subPatch, &patch->mPlaybackPatchHandle);
+    if (status != NO_ERROR) {
+        patch->mPlaybackPatchHandle = AUDIO_PATCH_HANDLE_NONE;
+        return status;
+    }
+
+    // use a pseudo LCM between input and output framecount
+    size_t playbackFrameCount = patch->mPlaybackThread->frameCount();
+    int playbackShift = __builtin_ctz(playbackFrameCount);
+    size_t recordFramecount = patch->mRecordThread->frameCount();
+    int shift = __builtin_ctz(recordFramecount);
+    if (playbackShift < shift) {
+        shift = playbackShift;
+    }
+    size_t frameCount = (playbackFrameCount * recordFramecount) >> shift;
+    ALOGV("createPatchConnections() playframeCount %d recordFramecount %d frameCount %d ",
+          playbackFrameCount, recordFramecount, frameCount);
+
+    // create a special record track to capture from record thread
+    uint32_t channelCount = patch->mPlaybackThread->channelCount();
+    audio_channel_mask_t inChannelMask = audio_channel_in_mask_from_count(channelCount);
+    audio_channel_mask_t outChannelMask = patch->mPlaybackThread->channelMask();
+    uint32_t sampleRate = patch->mPlaybackThread->sampleRate();
+    audio_format_t format = patch->mPlaybackThread->format();
+
+    patch->mPatchRecord = new RecordThread::PatchRecord(
+                                             patch->mRecordThread.get(),
+                                             sampleRate,
+                                             inChannelMask,
+                                             format,
+                                             frameCount,
+                                             NULL,
+                                             IAudioFlinger::TRACK_DEFAULT);
+    if (patch->mPatchRecord == 0) {
+        return NO_MEMORY;
+    }
+    status = patch->mPatchRecord->initCheck();
+    if (status != NO_ERROR) {
+        return status;
+    }
+    patch->mRecordThread->addPatchRecord(patch->mPatchRecord);
+
+    // create a special playback track to render to playback thread.
+    // this track is given the same buffer as the PatchRecord buffer
+    patch->mPatchTrack = new PlaybackThread::PatchTrack(
+                                           patch->mPlaybackThread.get(),
+                                           sampleRate,
+                                           outChannelMask,
+                                           format,
+                                           frameCount,
+                                           patch->mPatchRecord->buffer(),
+                                           IAudioFlinger::TRACK_DEFAULT);
+    if (patch->mPatchTrack == 0) {
+        return NO_MEMORY;
+    }
+    status = patch->mPatchTrack->initCheck();
+    if (status != NO_ERROR) {
+        return status;
+    }
+    patch->mPlaybackThread->addPatchTrack(patch->mPatchTrack);
+
+    // tie playback and record tracks together
+    patch->mPatchRecord->setPeerProxy(patch->mPatchTrack.get());
+    patch->mPatchTrack->setPeerProxy(patch->mPatchRecord.get());
+
+    // start capture and playback
+    patch->mPatchRecord->start(AudioSystem::SYNC_EVENT_NONE, 0);
+    patch->mPatchTrack->start();
+
+    return status;
+}
+
+void AudioFlinger::PatchPanel::clearPatchConnections(Patch *patch)
+{
+    sp<AudioFlinger> audioflinger = mAudioFlinger.promote();
+    if (audioflinger == 0) {
+        return;
+    }
+
+    ALOGV("clearPatchConnections() patch->mRecordPatchHandle %d patch->mPlaybackPatchHandle %d",
+          patch->mRecordPatchHandle, patch->mPlaybackPatchHandle);
+
+    if (patch->mPatchRecord != 0) {
+        patch->mPatchRecord->stop();
+    }
+    if (patch->mPatchTrack != 0) {
+        patch->mPatchTrack->stop();
+    }
+    if (patch->mRecordPatchHandle != AUDIO_PATCH_HANDLE_NONE) {
+        releaseAudioPatch(patch->mRecordPatchHandle);
+        patch->mRecordPatchHandle = AUDIO_PATCH_HANDLE_NONE;
+    }
+    if (patch->mPlaybackPatchHandle != AUDIO_PATCH_HANDLE_NONE) {
+        releaseAudioPatch(patch->mPlaybackPatchHandle);
+        patch->mPlaybackPatchHandle = AUDIO_PATCH_HANDLE_NONE;
+    }
+    if (patch->mRecordThread != 0) {
+        if (patch->mPatchRecord != 0) {
+            patch->mRecordThread->deletePatchRecord(patch->mPatchRecord);
+            patch->mPatchRecord.clear();
+        }
+        audioflinger->closeInputInternal_l(patch->mRecordThread);
+        patch->mRecordThread.clear();
+    }
+    if (patch->mPlaybackThread != 0) {
+        if (patch->mPatchTrack != 0) {
+            patch->mPlaybackThread->deletePatchTrack(patch->mPatchTrack);
+            patch->mPatchTrack.clear();
+        }
+        // if num sources == 2 we are reusing an existing playback thread so we do not close it
+        if (patch->mAudioPatch.num_sources != 2) {
+            audioflinger->closeOutputInternal_l(patch->mPlaybackThread);
+        }
+        patch->mPlaybackThread.clear();
+    }
+}
+
 /* Disconnect a patch */
 status_t AudioFlinger::PatchPanel::releaseAudioPatch(audio_patch_handle_t handle)
 {
@@ -315,8 +531,10 @@
     if (index == mPatches.size()) {
         return BAD_VALUE;
     }
+    Patch *removedPatch = mPatches[index];
+    mPatches.removeAt(index);
 
-    struct audio_patch *patch = &mPatches[index]->mAudioPatch;
+    struct audio_patch *patch = &removedPatch->mAudioPatch;
 
     switch (patch->sources[0].type) {
         case AUDIO_PORT_TYPE_DEVICE: {
@@ -327,13 +545,20 @@
                 status = BAD_VALUE;
                 break;
             }
+
+            if (patch->sinks[0].type == AUDIO_PORT_TYPE_DEVICE &&
+                    patch->sinks[0].ext.device.hw_module != src_module) {
+                clearPatchConnections(removedPatch);
+                break;
+            }
+
             AudioHwDevice *audioHwDevice = audioflinger->mAudioHwDevs.valueAt(index);
             if (audioHwDevice->version() >= AUDIO_DEVICE_API_VERSION_3_0) {
                 if (patch->sinks[0].type == AUDIO_PORT_TYPE_MIX) {
                     sp<ThreadBase> thread = audioflinger->checkRecordThread_l(
                                                                     patch->sinks[0].ext.mix.handle);
                     if (thread == 0) {
-                        ALOGW("createAudioPatch() bad capture I/O handle %d",
+                        ALOGW("releaseAudioPatch() bad capture I/O handle %d",
                                                                   patch->sinks[0].ext.mix.handle);
                         status = BAD_VALUE;
                         break;
@@ -389,8 +614,7 @@
             break;
     }
 
-    delete (mPatches[index]);
-    mPatches.removeAt(index);
+    delete removedPatch;
     return status;
 }
 
diff --git a/services/audioflinger/PatchPanel.h b/services/audioflinger/PatchPanel.h
index 7f78621..e31179c 100644
--- a/services/audioflinger/PatchPanel.h
+++ b/services/audioflinger/PatchPanel.h
@@ -21,6 +21,9 @@
 
 class PatchPanel : public RefBase {
 public:
+
+    class Patch;
+
     PatchPanel(const sp<AudioFlinger>& audioFlinger);
     virtual ~PatchPanel();
 
@@ -45,16 +48,31 @@
     /* Set audio port configuration */
     status_t setAudioPortConfig(const struct audio_port_config *config);
 
+    status_t createPatchConnections(Patch *patch,
+                                    const struct audio_patch *audioPatch);
+    void clearPatchConnections(Patch *patch);
+
     class Patch {
     public:
         Patch(const struct audio_patch *patch) :
-            mAudioPatch(*patch), mHandle(0), mHalHandle(0) {}
+            mAudioPatch(*patch), mHandle(AUDIO_PATCH_HANDLE_NONE),
+            mHalHandle(AUDIO_PATCH_HANDLE_NONE), mRecordPatchHandle(AUDIO_PATCH_HANDLE_NONE),
+            mPlaybackPatchHandle(AUDIO_PATCH_HANDLE_NONE) {}
+        ~Patch() {}
 
-        struct audio_patch mAudioPatch;
-        audio_patch_handle_t mHandle;
-        audio_patch_handle_t mHalHandle;
+        struct audio_patch              mAudioPatch;
+        audio_patch_handle_t            mHandle;
+        audio_patch_handle_t            mHalHandle;
+        sp<PlaybackThread>              mPlaybackThread;
+        sp<PlaybackThread::PatchTrack>  mPatchTrack;
+        sp<RecordThread>                mRecordThread;
+        sp<RecordThread::PatchRecord>   mPatchRecord;
+        audio_patch_handle_t            mRecordPatchHandle;
+        audio_patch_handle_t            mPlaybackPatchHandle;
+
     };
+
 private:
-    const wp<AudioFlinger>  mAudioFlinger;
-    SortedVector <Patch *> mPatches;
+    const wp<AudioFlinger>      mAudioFlinger;
+    SortedVector <Patch *>      mPatches;
 };
diff --git a/services/audioflinger/PlaybackTracks.h b/services/audioflinger/PlaybackTracks.h
index 79bdfe8..ee48276 100644
--- a/services/audioflinger/PlaybackTracks.h
+++ b/services/audioflinger/PlaybackTracks.h
@@ -29,10 +29,12 @@
                                 audio_format_t format,
                                 audio_channel_mask_t channelMask,
                                 size_t frameCount,
+                                void *buffer,
                                 const sp<IMemory>& sharedBuffer,
                                 int sessionId,
                                 int uid,
-                                IAudioFlinger::track_flags_t flags);
+                                IAudioFlinger::track_flags_t flags,
+                                track_type type);
     virtual             ~Track();
     virtual status_t    initCheck() const;
 
@@ -100,10 +102,6 @@
     bool isResumePending();
     void resumeAck();
 
-    bool isOutputTrack() const {
-        return (mStreamType == AUDIO_STREAM_CNT);
-    }
-
     sp<IMemory> sharedBuffer() const { return mSharedBuffer; }
 
     // framesWritten is cumulative, never reset, and is shared all tracks
@@ -115,7 +113,6 @@
     void triggerEvents(AudioSystem::sync_event_t type);
     void invalidate();
     bool isInvalid() const { return mIsInvalid; }
-    virtual bool isTimedTrack() const { return false; }
     int fastIndex() const { return mFastIndex; }
 
 protected:
@@ -163,7 +160,6 @@
     bool                mPreviousValid;
     uint32_t            mPreviousFramesWritten;
     AudioTimestamp      mPreviousTimestamp;
-
 };  // end of Track
 
 class TimedTrack : public Track {
@@ -195,7 +191,6 @@
     };
 
     // Mixer facing methods.
-    virtual bool isTimedTrack() const { return true; }
     virtual size_t framesReady() const;
 
     // AudioBufferProvider interface
@@ -296,3 +291,34 @@
     DuplicatingThread* const mSourceThread; // for waitTimeMs() in write()
     AudioTrackClientProxy*      mClientProxy;
 };  // end of OutputTrack
+
+// playback track, used by PatchPanel
+class PatchTrack : public Track, public PatchProxyBufferProvider {
+public:
+
+                        PatchTrack(PlaybackThread *playbackThread,
+                                   uint32_t sampleRate,
+                                   audio_channel_mask_t channelMask,
+                                   audio_format_t format,
+                                   size_t frameCount,
+                                   void *buffer,
+                                   IAudioFlinger::track_flags_t flags);
+    virtual             ~PatchTrack();
+
+    // AudioBufferProvider interface
+    virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer,
+                                   int64_t pts);
+    virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer);
+
+    // PatchProxyBufferProvider interface
+    virtual status_t    obtainBuffer(Proxy::Buffer* buffer,
+                                     const struct timespec *timeOut = NULL);
+    virtual void        releaseBuffer(Proxy::Buffer* buffer);
+
+            void setPeerProxy(PatchProxyBufferProvider *proxy) { mPeerProxy = proxy; }
+
+private:
+    sp<ClientProxy>             mProxy;
+    PatchProxyBufferProvider*   mPeerProxy;
+    struct timespec             mPeerTimeout;
+};  // end of PatchTrack
diff --git a/services/audioflinger/RecordTracks.h b/services/audioflinger/RecordTracks.h
index fe15571..204a9d6 100644
--- a/services/audioflinger/RecordTracks.h
+++ b/services/audioflinger/RecordTracks.h
@@ -28,9 +28,11 @@
                                 audio_format_t format,
                                 audio_channel_mask_t channelMask,
                                 size_t frameCount,
+                                void *buffer,
                                 int sessionId,
                                 int uid,
-                                IAudioFlinger::track_flags_t flags);
+                                IAudioFlinger::track_flags_t flags,
+                                track_type type);
     virtual             ~RecordTrack();
 
     virtual status_t    start(AudioSystem::sync_event_t event, int triggerSession);
@@ -93,3 +95,34 @@
             // used by resampler to find source frames
             ResamplerBufferProvider *mResamplerBufferProvider;
 };
+
+// playback track, used by PatchPanel
+class PatchRecord : virtual public RecordTrack, public PatchProxyBufferProvider {
+public:
+
+    PatchRecord(RecordThread *recordThread,
+                uint32_t sampleRate,
+                audio_channel_mask_t channelMask,
+                audio_format_t format,
+                size_t frameCount,
+                void *buffer,
+                IAudioFlinger::track_flags_t flags);
+    virtual             ~PatchRecord();
+
+    // AudioBufferProvider interface
+    virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer,
+                                   int64_t pts);
+    virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer);
+
+    // PatchProxyBufferProvider interface
+    virtual status_t    obtainBuffer(Proxy::Buffer *buffer,
+                                     const struct timespec *timeOut = NULL);
+    virtual void        releaseBuffer(Proxy::Buffer *buffer);
+
+    void setPeerProxy(PatchProxyBufferProvider *proxy) { mPeerProxy = proxy; }
+
+private:
+    sp<ClientProxy>             mProxy;
+    PatchProxyBufferProvider*   mPeerProxy;
+    struct timespec             mPeerTimeout;
+};  // end of PatchRecord
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index e0b664b..651c6ea 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -1146,6 +1146,18 @@
     }
 }
 
+void AudioFlinger::ThreadBase::getAudioPortConfig(struct audio_port_config *config)
+{
+    config->type = AUDIO_PORT_TYPE_MIX;
+    config->ext.mix.handle = mId;
+    config->sample_rate = mSampleRate;
+    config->format = mFormat;
+    config->channel_mask = mChannelMask;
+    config->config_mask = AUDIO_PORT_CONFIG_SAMPLE_RATE|AUDIO_PORT_CONFIG_CHANNEL_MASK|
+                            AUDIO_PORT_CONFIG_FORMAT;
+}
+
+
 // ----------------------------------------------------------------------------
 //      Playback
 // ----------------------------------------------------------------------------
@@ -1482,7 +1494,7 @@
         uint32_t strategy = AudioSystem::getStrategyForStream(streamType);
         for (size_t i = 0; i < mTracks.size(); ++i) {
             sp<Track> t = mTracks[i];
-            if (t != 0 && !t->isOutputTrack()) {
+            if (t != 0 && t->isExternalTrack()) {
                 uint32_t actual = AudioSystem::getStrategyForStream(t->streamType());
                 if (sessionId == t->sessionId() && strategy != actual) {
                     ALOGE("createTrack_l() mismatched strategy; expected %u but found %u",
@@ -1495,7 +1507,8 @@
 
         if (!isTimed) {
             track = new Track(this, client, streamType, sampleRate, format,
-                    channelMask, frameCount, sharedBuffer, sessionId, uid, *flags);
+                              channelMask, frameCount, NULL, sharedBuffer,
+                              sessionId, uid, *flags, TrackBase::TYPE_DEFAULT);
         } else {
             track = TimedTrack::create(this, client, streamType, sampleRate, format,
                     channelMask, frameCount, sharedBuffer, sessionId, uid);
@@ -1608,7 +1621,7 @@
         // the track is newly added, make sure it fills up all its
         // buffers before playing. This is to ensure the client will
         // effectively get the latency it requested.
-        if (!track->isOutputTrack()) {
+        if (track->isExternalTrack()) {
             TrackBase::track_state state = track->mState;
             mLock.unlock();
             status = AudioSystem::startOutput(mId, track->streamType(), track->sessionId());
@@ -2044,7 +2057,7 @@
     if (count > 0) {
         for (size_t i = 0 ; i < count ; i++) {
             const sp<Track>& track = tracksToRemove.itemAt(i);
-            if (!track->isOutputTrack()) {
+            if (track->isExternalTrack()) {
                 AudioSystem::stopOutput(mId, track->streamType(), track->sessionId());
 #ifdef ADD_BATTERY_DATA
                 // to track the speaker usage
@@ -2713,6 +2726,26 @@
     return status;
 }
 
+void AudioFlinger::PlaybackThread::addPatchTrack(const sp<PatchTrack>& track)
+{
+    Mutex::Autolock _l(mLock);
+    mTracks.add(track);
+}
+
+void AudioFlinger::PlaybackThread::deletePatchTrack(const sp<PatchTrack>& track)
+{
+    Mutex::Autolock _l(mLock);
+    destroyTrack_l(track);
+}
+
+void AudioFlinger::PlaybackThread::getAudioPortConfig(struct audio_port_config *config)
+{
+    ThreadBase::getAudioPortConfig(config);
+    config->role = AUDIO_PORT_ROLE_SOURCE;
+    config->ext.mix.hw_module = mOutput->audioHwDev->handle();
+    config->ext.mix.usecase.stream = AUDIO_STREAM_DEFAULT;
+}
+
 // ----------------------------------------------------------------------------
 
 AudioFlinger::MixerThread::MixerThread(const sp<AudioFlinger>& audioFlinger, AudioStreamOut* output,
@@ -5523,8 +5556,8 @@
         Mutex::Autolock _l(mLock);
 
         track = new RecordTrack(this, client, sampleRate,
-                      format, channelMask, frameCount, sessionId, uid,
-                      *flags);
+                      format, channelMask, frameCount, NULL, sessionId, uid,
+                      *flags, TrackBase::TYPE_DEFAULT);
 
         lStatus = track->initCheck();
         if (lStatus != NO_ERROR) {
@@ -5601,15 +5634,19 @@
         recordTrack->mState = TrackBase::STARTING_1;
         mActiveTracks.add(recordTrack);
         mActiveTracksGen++;
-        mLock.unlock();
-        status_t status = AudioSystem::startInput(mId);
-        mLock.lock();
-        // FIXME should verify that recordTrack is still in mActiveTracks
-        if (status != NO_ERROR) {
-            mActiveTracks.remove(recordTrack);
-            mActiveTracksGen++;
-            recordTrack->clearSyncStartEvent();
-            return status;
+        status_t status = NO_ERROR;
+        if (recordTrack->isExternalTrack()) {
+            mLock.unlock();
+            status = AudioSystem::startInput(mId);
+            mLock.lock();
+            // FIXME should verify that recordTrack is still in mActiveTracks
+            if (status != NO_ERROR) {
+                mActiveTracks.remove(recordTrack);
+                mActiveTracksGen++;
+                recordTrack->clearSyncStartEvent();
+                ALOGV("RecordThread::start error %d", status);
+                return status;
+            }
         }
         // Catch up with current buffer indices if thread is already running.
         // This is what makes a new client discard all buffered data.  If the track's mRsmpInFront
@@ -5634,7 +5671,9 @@
     }
 
 startError:
-    AudioSystem::stopInput(mId);
+    if (recordTrack->isExternalTrack()) {
+        AudioSystem::stopInput(mId);
+    }
     recordTrack->clearSyncStartEvent();
     // FIXME I wonder why we do not reset the state here?
     return status;
@@ -6177,5 +6216,24 @@
     return status;
 }
 
+void AudioFlinger::RecordThread::addPatchRecord(const sp<PatchRecord>& record)
+{
+    Mutex::Autolock _l(mLock);
+    mTracks.add(record);
+}
+
+void AudioFlinger::RecordThread::deletePatchRecord(const sp<PatchRecord>& record)
+{
+    Mutex::Autolock _l(mLock);
+    destroyTrack_l(record);
+}
+
+void AudioFlinger::RecordThread::getAudioPortConfig(struct audio_port_config *config)
+{
+    ThreadBase::getAudioPortConfig(config);
+    config->role = AUDIO_PORT_ROLE_SINK;
+    config->ext.mix.hw_module = mInput->audioHwDev->handle();
+    config->ext.mix.usecase.source = mAudioSource;
+}
 
 }; // namespace android
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 3b7257b..648502b 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -235,6 +235,7 @@
                 uint32_t    sampleRate() const { return mSampleRate; }
                 audio_channel_mask_t channelMask() const { return mChannelMask; }
                 audio_format_t format() const { return mHALFormat; }
+                uint32_t channelCount() const { return mChannelCount; }
                 // Called by AudioFlinger::frameCount(audio_io_handle_t output) and effects,
                 // and returns the [normal mix] buffer's frame count.
     virtual     size_t      frameCount() const = 0;
@@ -264,6 +265,7 @@
     virtual     status_t    createAudioPatch_l(const struct audio_patch *patch,
                                                audio_patch_handle_t *handle) = 0;
     virtual     status_t    releaseAudioPatch_l(const audio_patch_handle_t handle) = 0;
+    virtual     void        getAudioPortConfig(struct audio_port_config *config) = 0;
 
 
                 // see note at declaration of mStandby, mOutDevice and mInDevice
@@ -589,7 +591,12 @@
                 // Return's the HAL's frame count i.e. fast mixer buffer size.
                 size_t      frameCountHAL() const { return mFrameCount; }
 
-                status_t         getTimestamp_l(AudioTimestamp& timestamp);
+                status_t    getTimestamp_l(AudioTimestamp& timestamp);
+
+                void        addPatchTrack(const sp<PatchTrack>& track);
+                void        deletePatchTrack(const sp<PatchTrack>& track);
+
+    virtual     void        getAudioPortConfig(struct audio_port_config *config);
 
 protected:
     // updated by readOutputParameters_l()
@@ -876,6 +883,7 @@
                               ALOG_ASSERT(fastIndex < FastMixerState::kMaxFastTracks);
                               return mFastMixerDumpState.mTracks[fastIndex].mUnderruns;
                             }
+
 };
 
 class DirectOutputThread : public PlaybackThread {
@@ -1103,6 +1111,10 @@
     virtual status_t    createAudioPatch_l(const struct audio_patch *patch,
                                            audio_patch_handle_t *handle);
     virtual status_t    releaseAudioPatch_l(const audio_patch_handle_t handle);
+
+            void        addPatchRecord(const sp<PatchRecord>& record);
+            void        deletePatchRecord(const sp<PatchRecord>& record);
+
             void        readInputParameters_l();
     virtual uint32_t    getInputFramesLost();
 
@@ -1122,6 +1134,7 @@
 
     virtual size_t      frameCount() const { return mFrameCount; }
             bool        hasFastCapture() const { return mFastCapture != 0; }
+    virtual void        getAudioPortConfig(struct audio_port_config *config);
 
 private:
             // Enter standby if not already in standby, and set mStandby flag
diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h
index 4cba3fd..864daa5 100644
--- a/services/audioflinger/TrackBase.h
+++ b/services/audioflinger/TrackBase.h
@@ -44,6 +44,15 @@
         ALLOC_CBLK,     // allocate immediately after control block
         ALLOC_READONLY, // allocate from a separate read-only heap per thread
         ALLOC_PIPE,     // do not allocate; use the pipe buffer
+        ALLOC_LOCAL,    // allocate a local buffer
+        ALLOC_NONE,     // do not allocate:use the buffer passed to TrackBase constructor
+    };
+
+    enum track_type {
+        TYPE_DEFAULT,
+        TYPE_TIMED,
+        TYPE_OUTPUT,
+        TYPE_PATCH,
     };
 
                         TrackBase(ThreadBase *thread,
@@ -52,14 +61,15 @@
                                 audio_format_t format,
                                 audio_channel_mask_t channelMask,
                                 size_t frameCount,
-                                const sp<IMemory>& sharedBuffer,
+                                void *buffer,
                                 int sessionId,
                                 int uid,
                                 IAudioFlinger::track_flags_t flags,
                                 bool isOut,
-                                alloc_type alloc = ALLOC_CBLK);
+                                alloc_type alloc = ALLOC_CBLK,
+                                track_type type = TYPE_DEFAULT);
     virtual             ~TrackBase();
-    virtual status_t    initCheck() const { return getCblk() != 0 ? NO_ERROR : NO_MEMORY; }
+    virtual status_t    initCheck() const;
 
     virtual status_t    start(AudioSystem::sync_event_t event,
                              int triggerSession) = 0;
@@ -71,7 +81,12 @@
     virtual status_t    setSyncEvent(const sp<SyncEvent>& event);
 
             sp<IMemory> getBuffers() const { return mBufferMemory; }
+            void*       buffer() const { return mBuffer; }
             bool        isFastTrack() const { return (mFlags & IAudioFlinger::TRACK_FAST) != 0; }
+            bool        isTimedTrack() const { return (mType == TYPE_TIMED); }
+            bool        isOutputTrack() const { return (mType == TYPE_OUTPUT); }
+            bool        isPatchTrack() const { return (mType == TYPE_PATCH); }
+            bool        isExternalTrack() const { return !isOutputTrack() && !isPatchTrack(); }
 
 protected:
                         TrackBase(const TrackBase&);
@@ -150,4 +165,18 @@
     sp<NBAIO_Sink>      mTeeSink;
     sp<NBAIO_Source>    mTeeSource;
     bool                mTerminated;
+    track_type          mType;      // must be one of TYPE_DEFAULT, TYPE_OUTPUT, TYPE_PATCH ...
+};
+
+// PatchProxyBufferProvider interface is implemented by PatchTrack and PatchRecord.
+// it provides buffer access methods that map those of a ClientProxy (see AudioTrackShared.h)
+class PatchProxyBufferProvider
+{
+public:
+
+    virtual ~PatchProxyBufferProvider() {}
+
+    virtual status_t    obtainBuffer(Proxy::Buffer* buffer,
+                                     const struct timespec *requested = NULL) = 0;
+    virtual void        releaseBuffer(Proxy::Buffer* buffer) = 0;
 };
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index af761e4..e81697f 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -68,12 +68,13 @@
             audio_format_t format,
             audio_channel_mask_t channelMask,
             size_t frameCount,
-            const sp<IMemory>& sharedBuffer,
+            void *buffer,
             int sessionId,
             int clientUid,
             IAudioFlinger::track_flags_t flags,
             bool isOut,
-            alloc_type alloc)
+            alloc_type alloc,
+            track_type type)
     :   RefBase(),
         mThread(thread),
         mClient(client),
@@ -94,7 +95,8 @@
         mIsOut(isOut),
         mServerProxy(NULL),
         mId(android_atomic_inc(&nextTrackId)),
-        mTerminated(false)
+        mTerminated(false),
+        mType(type)
 {
     // if the caller is us, trust the specified uid
     if (IPCThreadState::self()->getCallingPid() != getpid_cached || clientUid == -1) {
@@ -108,16 +110,10 @@
     // battery usage on it.
     mUid = clientUid;
 
-    // client == 0 implies sharedBuffer == 0
-    ALOG_ASSERT(!(client == 0 && sharedBuffer != 0));
-
-    ALOGV_IF(sharedBuffer != 0, "sharedBuffer: %p, size: %d", sharedBuffer->pointer(),
-            sharedBuffer->size());
-
     // ALOGD("Creating track with %d buffers @ %d bytes", bufferCount, bufferSize);
     size_t size = sizeof(audio_track_cblk_t);
-    size_t bufferSize = (sharedBuffer == 0 ? roundup(frameCount) : frameCount) * mFrameSize;
-    if (sharedBuffer == 0 && alloc == ALLOC_CBLK) {
+    size_t bufferSize = (buffer == NULL ? roundup(frameCount) : frameCount) * mFrameSize;
+    if (buffer == NULL && alloc == ALLOC_CBLK) {
         size += bufferSize;
     }
 
@@ -166,16 +162,22 @@
             break;
         case ALLOC_CBLK:
             // clear all buffers
-            if (sharedBuffer == 0) {
+            if (buffer == NULL) {
                 mBuffer = (char*)mCblk + sizeof(audio_track_cblk_t);
                 memset(mBuffer, 0, bufferSize);
             } else {
-                mBuffer = sharedBuffer->pointer();
+                mBuffer = buffer;
 #if 0
                 mCblk->mFlags = CBLK_FORCEREADY;    // FIXME hack, need to fix the track ready logic
 #endif
             }
             break;
+        case ALLOC_LOCAL:
+            mBuffer = calloc(1, bufferSize);
+            break;
+        case ALLOC_NONE:
+            mBuffer = buffer;
+            break;
         }
 
 #ifdef TEE_SINK
@@ -200,6 +202,17 @@
     }
 }
 
+status_t AudioFlinger::ThreadBase::TrackBase::initCheck() const
+{
+    status_t status;
+    if (mType == TYPE_OUTPUT || mType == TYPE_PATCH) {
+        status = cblk() != NULL ? NO_ERROR : NO_MEMORY;
+    } else {
+        status = getCblk() != 0 ? NO_ERROR : NO_MEMORY;
+    }
+    return status;
+}
+
 AudioFlinger::ThreadBase::TrackBase::~TrackBase()
 {
 #ifdef TEE_SINK
@@ -364,12 +377,17 @@
             audio_format_t format,
             audio_channel_mask_t channelMask,
             size_t frameCount,
+            void *buffer,
             const sp<IMemory>& sharedBuffer,
             int sessionId,
             int uid,
-            IAudioFlinger::track_flags_t flags)
-    :   TrackBase(thread, client, sampleRate, format, channelMask, frameCount, sharedBuffer,
-            sessionId, uid, flags, true /*isOut*/),
+            IAudioFlinger::track_flags_t flags,
+            track_type type)
+    :   TrackBase(thread, client, sampleRate, format, channelMask, frameCount,
+                  (sharedBuffer != 0) ? sharedBuffer->pointer() : buffer,
+                  sessionId, uid, flags, true /*isOut*/,
+                  (type == TYPE_PATCH) ? ( buffer == NULL ? ALLOC_LOCAL : ALLOC_NONE) : ALLOC_CBLK,
+                  type),
     mFillingUpStatus(FS_INVALID),
     // mRetryCount initialized later when needed
     mSharedBuffer(sharedBuffer),
@@ -389,13 +407,19 @@
     mPreviousFramesWritten(0)
     // mPreviousTimestamp
 {
+    // client == 0 implies sharedBuffer == 0
+    ALOG_ASSERT(!(client == 0 && sharedBuffer != 0));
+
+    ALOGV_IF(sharedBuffer != 0, "sharedBuffer: %p, size: %d", sharedBuffer->pointer(),
+            sharedBuffer->size());
+
     if (mCblk == NULL) {
         return;
     }
 
     if (sharedBuffer == 0) {
         mAudioTrackServerProxy = new AudioTrackServerProxy(mCblk, mBuffer, frameCount,
-                mFrameSize);
+                mFrameSize, !isExternalTrack(), sampleRate);
     } else {
         mAudioTrackServerProxy = new StaticAudioTrackServerProxy(mCblk, mBuffer, frameCount,
                 mFrameSize);
@@ -463,7 +487,7 @@
             Mutex::Autolock _l(thread->mLock);
             PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
             bool wasActive = playbackThread->destroyTrack_l(this);
-            if (!isOutputTrack() && !wasActive) {
+            if (isExternalTrack() && !wasActive) {
                 AudioSystem::releaseOutput(thread->id());
             }
         }
@@ -1122,7 +1146,8 @@
             int sessionId,
             int uid)
     : Track(thread, client, streamType, sampleRate, format, channelMask,
-            frameCount, sharedBuffer, sessionId, uid, IAudioFlinger::TRACK_TIMED),
+            frameCount, (sharedBuffer != 0) ? sharedBuffer->pointer() : NULL, sharedBuffer,
+                    sessionId, uid, IAudioFlinger::TRACK_TIMED, TYPE_TIMED),
       mQueueHeadInFlight(false),
       mTrimQueueHeadOnRelease(false),
       mFramesPendingInQueue(0),
@@ -1617,7 +1642,7 @@
             size_t frameCount,
             int uid)
     :   Track(playbackThread, NULL, AUDIO_STREAM_CNT, sampleRate, format, channelMask, frameCount,
-                NULL, 0, uid, IAudioFlinger::TRACK_DEFAULT),
+                NULL, 0, 0, uid, IAudioFlinger::TRACK_DEFAULT, TYPE_OUTPUT),
     mActive(false), mSourceThread(sourceThread), mClientProxy(NULL)
 {
 
@@ -1825,6 +1850,75 @@
 }
 
 
+AudioFlinger::PlaybackThread::PatchTrack::PatchTrack(PlaybackThread *playbackThread,
+                                                     uint32_t sampleRate,
+                                                     audio_channel_mask_t channelMask,
+                                                     audio_format_t format,
+                                                     size_t frameCount,
+                                                     void *buffer,
+                                                     IAudioFlinger::track_flags_t flags)
+    :   Track(playbackThread, NULL, AUDIO_STREAM_CNT, sampleRate, format, channelMask, frameCount,
+              buffer, 0, 0, getuid(), flags, TYPE_PATCH),
+              mProxy(new ClientProxy(mCblk, mBuffer, frameCount, mFrameSize, true, true))
+{
+    uint64_t mixBufferNs = ((uint64_t)2 * playbackThread->frameCount() * 1000000000) /
+                                                                    playbackThread->sampleRate();
+    mPeerTimeout.tv_sec = mixBufferNs / 1000000000;
+    mPeerTimeout.tv_nsec = (int) (mixBufferNs % 1000000000);
+
+    ALOGV("PatchTrack %p sampleRate %d mPeerTimeout %d.%03d sec",
+                                      this, sampleRate,
+                                      (int)mPeerTimeout.tv_sec,
+                                      (int)(mPeerTimeout.tv_nsec / 1000000));
+}
+
+AudioFlinger::PlaybackThread::PatchTrack::~PatchTrack()
+{
+}
+
+// AudioBufferProvider interface
+status_t AudioFlinger::PlaybackThread::PatchTrack::getNextBuffer(
+        AudioBufferProvider::Buffer* buffer, int64_t pts)
+{
+    ALOG_ASSERT(mPeerProxy != 0, "PatchTrack::getNextBuffer() called without peer proxy");
+    Proxy::Buffer buf;
+    buf.mFrameCount = buffer->frameCount;
+    status_t status = mPeerProxy->obtainBuffer(&buf, &mPeerTimeout);
+    ALOGV_IF(status != NO_ERROR, "PatchTrack() %p getNextBuffer status %d", this, status);
+    if (buf.mFrameCount == 0) {
+        return WOULD_BLOCK;
+    }
+    buffer->frameCount = buf.mFrameCount;
+    status = Track::getNextBuffer(buffer, pts);
+    return status;
+}
+
+void AudioFlinger::PlaybackThread::PatchTrack::releaseBuffer(AudioBufferProvider::Buffer* buffer)
+{
+    ALOG_ASSERT(mPeerProxy != 0, "PatchTrack::releaseBuffer() called without peer proxy");
+    Proxy::Buffer buf;
+    buf.mFrameCount = buffer->frameCount;
+    buf.mRaw = buffer->raw;
+    mPeerProxy->releaseBuffer(&buf);
+    TrackBase::releaseBuffer(buffer);
+}
+
+status_t AudioFlinger::PlaybackThread::PatchTrack::obtainBuffer(Proxy::Buffer* buffer,
+                                                                const struct timespec *timeOut)
+{
+    return mProxy->obtainBuffer(buffer, timeOut);
+}
+
+void AudioFlinger::PlaybackThread::PatchTrack::releaseBuffer(Proxy::Buffer* buffer)
+{
+    mProxy->releaseBuffer(buffer);
+    if (android_atomic_and(~CBLK_DISABLED, &mCblk->mFlags) & CBLK_DISABLED) {
+        ALOGW("PatchTrack::releaseBuffer() disabled due to previous underrun, restarting");
+        start();
+    }
+    android_atomic_or(CBLK_FORCEREADY, &mCblk->mFlags);
+}
+
 // ----------------------------------------------------------------------------
 //      Record
 // ----------------------------------------------------------------------------
@@ -1872,13 +1966,18 @@
             audio_format_t format,
             audio_channel_mask_t channelMask,
             size_t frameCount,
+            void *buffer,
             int sessionId,
             int uid,
-            IAudioFlinger::track_flags_t flags)
+            IAudioFlinger::track_flags_t flags,
+            track_type type)
     :   TrackBase(thread, client, sampleRate, format,
-                  channelMask, frameCount, 0 /*sharedBuffer*/, sessionId, uid,
+                  channelMask, frameCount, buffer, sessionId, uid,
                   flags, false /*isOut*/,
-                  flags & IAudioFlinger::TRACK_FAST ? ALLOC_PIPE : ALLOC_CBLK),
+                  (type == TYPE_DEFAULT) ?
+                          ((flags & IAudioFlinger::TRACK_FAST) ? ALLOC_PIPE : ALLOC_CBLK) :
+                          ((buffer == NULL) ? ALLOC_LOCAL : ALLOC_NONE),
+                  type),
         mOverflow(false), mResampler(NULL), mRsmpOutBuffer(NULL), mRsmpOutFrameCount(0),
         // See real initialization of mRsmpInFront at RecordThread::start()
         mRsmpInUnrel(0), mRsmpInFront(0), mFramesToDrop(0), mResamplerBufferProvider(NULL)
@@ -1887,7 +1986,8 @@
         return;
     }
 
-    mServerProxy = new AudioRecordServerProxy(mCblk, mBuffer, frameCount, mFrameSize);
+    mServerProxy = new AudioRecordServerProxy(mCblk, mBuffer, frameCount,
+                                              mFrameSize, !isExternalTrack());
 
     uint32_t channelCount = audio_channel_count_from_in_mask(channelMask);
     // FIXME I don't understand either of the channel count checks
@@ -1949,7 +2049,7 @@
     sp<ThreadBase> thread = mThread.promote();
     if (thread != 0) {
         RecordThread *recordThread = (RecordThread *)thread.get();
-        if (recordThread->stop(this)) {
+        if (recordThread->stop(this) && isExternalTrack()) {
             AudioSystem::stopInput(recordThread->id());
         }
     }
@@ -1962,10 +2062,12 @@
     {
         sp<ThreadBase> thread = mThread.promote();
         if (thread != 0) {
-            if (mState == ACTIVE || mState == RESUMING) {
-                AudioSystem::stopInput(thread->id());
+            if (isExternalTrack()) {
+                if (mState == ACTIVE || mState == RESUMING) {
+                    AudioSystem::stopInput(thread->id());
+                }
+                AudioSystem::releaseInput(thread->id());
             }
-            AudioSystem::releaseInput(thread->id());
             Mutex::Autolock _l(thread->mLock);
             RecordThread *recordThread = (RecordThread *) thread.get();
             recordThread->destroyTrack_l(this);
@@ -2027,4 +2129,70 @@
     mFramesToDrop = 0;
 }
 
+
+AudioFlinger::RecordThread::PatchRecord::PatchRecord(RecordThread *recordThread,
+                                                     uint32_t sampleRate,
+                                                     audio_channel_mask_t channelMask,
+                                                     audio_format_t format,
+                                                     size_t frameCount,
+                                                     void *buffer,
+                                                     IAudioFlinger::track_flags_t flags)
+    :   RecordTrack(recordThread, NULL, sampleRate, format, channelMask, frameCount,
+                buffer, 0, getuid(), flags, TYPE_PATCH),
+                mProxy(new ClientProxy(mCblk, mBuffer, frameCount, mFrameSize, false, true))
+{
+    uint64_t mixBufferNs = ((uint64_t)2 * recordThread->frameCount() * 1000000000) /
+                                                                recordThread->sampleRate();
+    mPeerTimeout.tv_sec = mixBufferNs / 1000000000;
+    mPeerTimeout.tv_nsec = (int) (mixBufferNs % 1000000000);
+
+    ALOGV("PatchRecord %p sampleRate %d mPeerTimeout %d.%03d sec",
+                                      this, sampleRate,
+                                      (int)mPeerTimeout.tv_sec,
+                                      (int)(mPeerTimeout.tv_nsec / 1000000));
+}
+
+AudioFlinger::RecordThread::PatchRecord::~PatchRecord()
+{
+}
+
+// AudioBufferProvider interface
+status_t AudioFlinger::RecordThread::PatchRecord::getNextBuffer(
+                                                  AudioBufferProvider::Buffer* buffer, int64_t pts)
+{
+    ALOG_ASSERT(mPeerProxy != 0, "PatchRecord::getNextBuffer() called without peer proxy");
+    Proxy::Buffer buf;
+    buf.mFrameCount = buffer->frameCount;
+    status_t status = mPeerProxy->obtainBuffer(&buf, &mPeerTimeout);
+    ALOGV_IF(status != NO_ERROR,
+             "PatchRecord() %p mPeerProxy->obtainBuffer status %d", this, status);
+    if (buf.mFrameCount == 0) {
+        return WOULD_BLOCK;
+    }
+    buffer->frameCount = buf.mFrameCount;
+    status = RecordTrack::getNextBuffer(buffer, pts);
+    return status;
+}
+
+void AudioFlinger::RecordThread::PatchRecord::releaseBuffer(AudioBufferProvider::Buffer* buffer)
+{
+    ALOG_ASSERT(mPeerProxy != 0, "PatchRecord::releaseBuffer() called without peer proxy");
+    Proxy::Buffer buf;
+    buf.mFrameCount = buffer->frameCount;
+    buf.mRaw = buffer->raw;
+    mPeerProxy->releaseBuffer(&buf);
+    TrackBase::releaseBuffer(buffer);
+}
+
+status_t AudioFlinger::RecordThread::PatchRecord::obtainBuffer(Proxy::Buffer* buffer,
+                                                               const struct timespec *timeOut)
+{
+    return mProxy->obtainBuffer(buffer, timeOut);
+}
+
+void AudioFlinger::RecordThread::PatchRecord::releaseBuffer(Proxy::Buffer* buffer)
+{
+    mProxy->releaseBuffer(buffer);
+}
+
 }; // namespace android