Add a PipeWire Pulse virtual sink and forwarder for compressed offload, 
enabling MP3 compressed Pulse streams to be routed to a PAL compressed sink.

Signed-off-by: Harsh Rai <[email protected]>
---
 .../0001-pipewire-compress-offload.patch      | 1343 +++++++++++++++++
 .../pipewire/pipewire_1.6.7.bb                |    1 +
 2 files changed, 1344 insertions(+)
 create mode 100644 
meta-multimedia/recipes-multimedia/pipewire/pipewire/0001-pipewire-compress-offload.patch

diff --git 
a/meta-multimedia/recipes-multimedia/pipewire/pipewire/0001-pipewire-compress-offload.patch
 
b/meta-multimedia/recipes-multimedia/pipewire/pipewire/0001-pipewire-compress-offload.patch
new file mode 100644
index 0000000000..9a60e5127a
--- /dev/null
+++ 
b/meta-multimedia/recipes-multimedia/pipewire/pipewire/0001-pipewire-compress-offload.patch
@@ -0,0 +1,1343 @@
+From 345e4b033c2ff69ab7aa02a123f6041b878f7fb9 Mon Sep 17 00:00:00 2001
+From: Harsh Rai <[email protected]>
+Date: Tue, 16 Jun 2026 19:21:37 +0530
+Subject: [PATCH] pulse: enable MP3 compressed offload via Pulse
+
+Add PipeWire Pulse virtual sink and forwarder for offload.
+Route compressed Pulse streams to PAL compressed sink.
+
+Upstream-Status: Submitted
+
+Signed-off-by: Harsh Rai <[email protected]>
+---
+ src/modules/meson.build                       |   2 +
+ .../module-compress-offload-forwarder.c       | 649 ++++++++++++++++++
+ .../modules/module-compress-offload-sink.c    | 293 ++++++++
+ .../module-protocol-pulse/pulse-server.c      | 254 ++++++-
+ 4 files changed, 1187 insertions(+), 11 deletions(-)
+ create mode 100644 
src/modules/module-protocol-pulse/modules/module-compress-offload-forwarder.c
+ create mode 100644 
src/modules/module-protocol-pulse/modules/module-compress-offload-sink.c
+
+diff --git a/src/modules/meson.build b/src/modules/meson.build
+index 59f46ae..900871d 100644
+--- a/src/modules/meson.build
++++ b/src/modules/meson.build
+@@ -324,6 +324,8 @@ pipewire_module_protocol_pulse_sources = [
+   'module-protocol-pulse/modules/module-alsa-source.c',
+   'module-protocol-pulse/modules/module-always-sink.c',
+   'module-protocol-pulse/modules/module-combine-sink.c',
++  'module-protocol-pulse/modules/module-compress-offload-sink.c',
++  'module-protocol-pulse/modules/module-compress-offload-forwarder.c',
+   'module-protocol-pulse/modules/module-device-manager.c',
+   'module-protocol-pulse/modules/module-device-restore.c',
+   'module-protocol-pulse/modules/module-echo-cancel.c',
+diff --git 
a/src/modules/module-protocol-pulse/modules/module-compress-offload-forwarder.c 
b/src/modules/module-protocol-pulse/modules/module-compress-offload-forwarder.c
+new file mode 100644
+index 0000000..33054e4
+--- /dev/null
++++ 
b/src/modules/module-protocol-pulse/modules/module-compress-offload-forwarder.c
+@@ -0,0 +1,649 @@
++/* PipeWire */
++/* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */
++/* SPDX-License-Identifier: BSD-3-Clause-Clear */
++
++#include <errno.h>
++#include <limits.h>
++#include <stdint.h>
++#include <stdlib.h>
++#include <string.h>
++
++#include <spa/param/audio/compressed.h>
++#include <spa/param/audio/format-utils.h>
++#include <spa/param/audio/raw.h>
++#include <spa/utils/result.h>
++#include <spa/utils/string.h>
++#include <pipewire/pipewire.h>
++
++#include "../module.h"
++
++#define NAME "compress-offload-forwarder"
++
++#define DEFAULT_CODEC "mp3"
++#define DEFAULT_RATE 48000u
++#define DEFAULT_CHANNELS 2u
++#define DEFAULT_BITRATE 128000u
++#define DEFAULT_BLOCK_SIZE (16u * 1024u)
++
++static const char *const forwarder_options =
++      "source_sink=<Pulse-visible compress sink node.name> "
++      "target_sink=<PAL compress sink node.name> "
++      "codec=<codec name, default mp3> "
++      "rate=<sample rate, default 48000> "
++      "channels=<number of channels, default 2> "
++      "bitrate=<bit rate, default 128000> "
++      "block_size=<buffer size in bytes, default 16384>";
++
++PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
++#define PW_LOG_TOPIC_DEFAULT mod_topic
++
++struct forwarder_data {
++      struct pw_core *core;
++      struct pw_loop *loop;
++      struct spa_source *activate_event;
++      struct spa_hook core_listener;
++      struct pw_registry *registry;
++      struct spa_hook registry_listener;
++
++      struct pw_stream *capture;
++      struct spa_hook capture_listener;
++      struct pw_stream *playback;
++      struct spa_hook playback_listener;
++
++      char source_name[256];
++      char monitor_name[280];
++      char target_name[256];
++      char codec[32];
++      uint32_t rate;
++      uint32_t channels;
++      uint32_t bitrate;
++      uint32_t block_size;
++      uint32_t target_id;
++      uint8_t *pending_data;
++      uint32_t pending_size;
++      uint32_t pending_offset;
++      uint8_t capture_streaming:1;
++      uint8_t logged_first_buffer:1;
++      uint8_t logged_first_output_buffer:1;
++      uint8_t logged_silence_buffer:1;
++      uint8_t activate_pending:1;
++      uint8_t playback_active:1;
++      uint8_t playback_failed:1;
++};
++
++static int create_playback_stream(struct forwarder_data *d);
++static void set_playback_active(struct forwarder_data *d, bool active);
++static void maybe_create_playback_stream(struct forwarder_data *d, const char 
*reason);
++
++static void request_playback_activation(struct forwarder_data *d)
++{
++      if (d->activate_event == NULL || d->activate_pending)
++              return;
++      d->activate_pending = true;
++      pw_loop_signal_event(d->loop, d->activate_event);
++}
++
++static void playback_activate_event(void *data, uint64_t count)
++{
++      struct forwarder_data *d = data;
++
++      d->activate_pending = false;
++      maybe_create_playback_stream(d, "first-audio-buffer");
++}
++
++static bool buffer_is_zeroed(const void *data, uint32_t size)
++{
++      const uint8_t *bytes = data;
++      uint32_t index;
++
++      for (index = 0; index < size; index++) {
++              if (bytes[index] != 0)
++                      return false;
++      }
++      return true;
++}
++
++static void clear_pending_data(struct forwarder_data *d)
++{
++      free(d->pending_data);
++      d->pending_data = NULL;
++      d->pending_size = 0;
++      d->pending_offset = 0;
++}
++
++static void compact_pending_data(struct forwarder_data *d)
++{
++      uint32_t remaining;
++
++      if (d->pending_offset == 0)
++              return;
++      if (d->pending_offset >= d->pending_size) {
++              clear_pending_data(d);
++              return;
++      }
++      remaining = d->pending_size - d->pending_offset;
++      memmove(d->pending_data, d->pending_data + d->pending_offset, 
remaining);
++      d->pending_size = remaining;
++      d->pending_offset = 0;
++}
++
++static int append_pending_data(struct forwarder_data *d, const void *data, 
uint32_t size)
++{
++      uint8_t *pending_data;
++
++      if (size == 0)
++              return 0;
++      compact_pending_data(d);
++      if (d->pending_size > UINT32_MAX - size)
++              return -EOVERFLOW;
++      pending_data = realloc(d->pending_data, d->pending_size + size);
++      if (pending_data == NULL)
++              return -errno;
++      d->pending_data = pending_data;
++      memcpy(d->pending_data + d->pending_size, data, size);
++      d->pending_size += size;
++      return 0;
++}
++
++static void maybe_create_playback_stream(struct forwarder_data *d, const char 
*reason)
++{
++      int res;
++
++      if (!d->capture_streaming)
++              return;
++      if (d->playback != NULL) {
++              set_playback_active(d, true);
++              return;
++      }
++      if (d->target_id == PW_ID_ANY) {
++              pw_log_debug("compress-forwarder target:%s not resolved yet, 
defer output create (%s)",
++                              d->target_name, reason);
++              return;
++      }
++      res = create_playback_stream(d);
++      if (res < 0)
++              pw_log_error("compress-forwarder target:%s create failed (%s): 
%s",
++                              d->target_name, reason, spa_strerror(res));
++      else
++              set_playback_active(d, true);
++}
++
++static void destroy_playback_stream(struct forwarder_data *d)
++{
++      clear_pending_data(d);
++      if (d->playback == NULL)
++              return;
++      spa_hook_remove(&d->playback_listener);
++      pw_stream_destroy(d->playback);
++      d->playback = NULL;
++      d->playback_active = false;
++      d->playback_failed = false;
++}
++
++static const struct spa_pod *build_raw_format(struct forwarder_data *d,
++              struct spa_pod_builder *builder, uint32_t id)
++{
++      struct spa_audio_info_raw info;
++
++      spa_zero(info);
++      info.format = SPA_AUDIO_FORMAT_S16_LE;
++      info.rate = d->rate;
++      info.channels = d->channels;
++      if (info.channels == 1) {
++              info.position[0] = SPA_AUDIO_CHANNEL_MONO;
++      } else {
++              info.channels = 2;
++              info.position[0] = SPA_AUDIO_CHANNEL_FL;
++              info.position[1] = SPA_AUDIO_CHANNEL_FR;
++      }
++      return spa_format_audio_raw_build(builder, id, &info);
++}
++
++static const struct spa_pod *build_encoded_format(struct forwarder_data *d,
++              struct spa_pod_builder *builder, uint32_t id)
++{
++      struct spa_audio_info info;
++
++      spa_zero(info);
++      info.media_type = SPA_MEDIA_TYPE_audio;
++      info.media_subtype = SPA_MEDIA_SUBTYPE_mp3;
++      info.info.mp3.rate = d->rate;
++      info.info.mp3.channels = d->channels;
++      info.info.mp3.channel_mode = d->channels == 1 ?
++              SPA_AUDIO_MP3_CHANNEL_MODE_MONO : 
SPA_AUDIO_MP3_CHANNEL_MODE_STEREO;
++
++      return spa_format_audio_build(builder, id, &info);
++}
++
++static void playback_state_changed(void *data, enum pw_stream_state old,
++              enum pw_stream_state state, const char *error)
++{
++      struct forwarder_data *d = data;
++
++      pw_log_debug("compress-forwarder target:%s state %d -> %d%s%s",
++                      d->target_name, old, state, error ? ": " : "", error ? 
error : "");
++      if (state == PW_STREAM_STATE_ERROR)
++              d->playback_failed = true;
++}
++
++static void set_playback_active(struct forwarder_data *d, bool active)
++{
++      if (d->playback == NULL || d->playback_active == active)
++              return;
++      pw_log_debug("compress-forwarder target:%s active=%d", d->target_name, 
active);
++      pw_stream_set_active(d->playback, active);
++      d->playback_active = active;
++}
++
++static void capture_state_changed(void *data, enum pw_stream_state old,
++              enum pw_stream_state state, const char *error)
++{
++      struct forwarder_data *d = data;
++
++      pw_log_debug("compress-forwarder source:%s state %d -> %d%s%s",
++                      d->monitor_name, old, state, error ? ": " : "", error ? 
error : "");
++      if (d->playback_failed)
++              destroy_playback_stream(d);
++      d->capture_streaming = state == PW_STREAM_STATE_STREAMING;
++      if (state == PW_STREAM_STATE_PAUSED || state == PW_STREAM_STATE_ERROR)
++              set_playback_active(d, false);
++}
++
++static void capture_process(void *data)
++{
++      struct forwarder_data *d = data;
++      struct pw_buffer *in_buf, *out_buf;
++      struct spa_data *in_data, *out_data;
++      uint32_t in_offset, in_size, out_size, copy_size;
++      const void *input_data;
++      const void *copy_data;
++      uint32_t copy_available;
++      int res;
++
++      in_buf = pw_stream_dequeue_buffer(d->capture);
++      if (in_buf == NULL)
++              return;
++
++      in_data = &in_buf->buffer->datas[0];
++      if (in_data->data == NULL || in_data->chunk == NULL) {
++              pw_stream_queue_buffer(d->capture, in_buf);
++              return;
++      }
++      in_offset = SPA_MIN(in_data->chunk->offset, in_data->maxsize);
++      in_size = SPA_MIN(in_data->chunk->size, in_data->maxsize - in_offset);
++      input_data = SPA_PTROFF(in_data->data, in_offset, void);
++      if (in_size == 0) {
++              pw_stream_queue_buffer(d->capture, in_buf);
++              return;
++      }
++      if (buffer_is_zeroed(input_data, in_size)) {
++              if (!d->logged_silence_buffer) {
++                      pw_log_debug("compress-forwarder source:%s ignoring 
zeroed startup buffer size=%u flags=0x%x",
++                                      d->monitor_name, in_size, 
in_data->chunk->flags);
++                      d->logged_silence_buffer = true;
++              }
++              pw_stream_queue_buffer(d->capture, in_buf);
++              return;
++      }
++      if (!d->logged_first_buffer) {
++              const uint8_t *bytes = SPA_PTROFF(in_data->data, in_offset, 
const uint8_t);
++              pw_log_debug("compress-forwarder source:%s first non-silence 
buffer size=%u offset=%u flags=0x%x",
++                              d->monitor_name, in_size, in_offset, 
in_data->chunk->flags);
++              pw_log_debug("compress-forwarder source:%s first non-silence 
bytes=%02x %02x %02x %02x %02x %02x %02x %02x",
++                              d->monitor_name,
++                              in_size > 0 ? bytes[0] : 0,
++                              in_size > 1 ? bytes[1] : 0,
++                              in_size > 2 ? bytes[2] : 0,
++                              in_size > 3 ? bytes[3] : 0,
++                              in_size > 4 ? bytes[4] : 0,
++                              in_size > 5 ? bytes[5] : 0,
++                              in_size > 6 ? bytes[6] : 0,
++                              in_size > 7 ? bytes[7] : 0);
++              d->logged_first_buffer = true;
++      }
++
++      if (d->playback_failed)
++              destroy_playback_stream(d);
++
++      res = append_pending_data(d, input_data, in_size);
++      if (res < 0) {
++              pw_log_error("compress-forwarder source:%s failed to preserve 
input before target write: %s",
++                              d->monitor_name, spa_strerror(res));
++              pw_stream_queue_buffer(d->capture, in_buf);
++              return;
++      }
++
++      if (d->playback == NULL) {
++              request_playback_activation(d);
++              if (d->playback == NULL) {
++                      pw_log_debug("compress-forwarder source:%s preserved %u 
bytes pending target create total=%u",
++                                      d->monitor_name, in_size, 
d->pending_size - d->pending_offset);
++                      pw_stream_queue_buffer(d->capture, in_buf);
++                      return;
++              }
++      }
++      if (!d->playback_active)
++              set_playback_active(d, true);
++      if (d->playback_failed || !d->playback_active) {
++              pw_log_debug("compress-forwarder source:%s preserved %u bytes 
pending inactive target total=%u",
++                              d->monitor_name, in_size, d->pending_size - 
d->pending_offset);
++              pw_stream_queue_buffer(d->capture, in_buf);
++              return;
++      }
++
++      out_buf = pw_stream_dequeue_buffer(d->playback);
++      if (out_buf == NULL) {
++              pw_log_debug("compress-forwarder source:%s preserved %u bytes 
pending output buffer total=%u",
++                              d->monitor_name, in_size, d->pending_size - 
d->pending_offset);
++              pw_stream_queue_buffer(d->capture, in_buf);
++              return;
++      }
++
++      out_data = &out_buf->buffer->datas[0];
++      if (out_data->data == NULL || out_data->chunk == NULL) {
++              pw_stream_queue_buffer(d->playback, out_buf);
++              pw_stream_queue_buffer(d->capture, in_buf);
++              return;
++      }
++
++      out_size = out_data->maxsize;
++      if (out_size == 0) {
++              pw_stream_queue_buffer(d->playback, out_buf);
++              pw_stream_queue_buffer(d->capture, in_buf);
++              return;
++      }
++      copy_data = d->pending_data + d->pending_offset;
++      copy_available = d->pending_size - d->pending_offset;
++      copy_size = SPA_MIN(copy_available, out_size);
++      memcpy(out_data->data, copy_data, copy_size);
++      d->pending_offset += copy_size;
++      if (d->pending_offset >= d->pending_size)
++              clear_pending_data(d);
++
++      out_data->chunk->offset = 0;
++      out_data->chunk->size = copy_size;
++      out_data->chunk->stride = 1;
++      out_buf->size = copy_size;
++
++      if (!d->logged_first_output_buffer) {
++              const uint8_t *out_bytes = out_data->data;
++              pw_log_debug("compress-forwarder target:%s queue buffer 
copy_size=%u out_max=%u chunk=%u/%u/%d bytes=%02x %02x %02x %02x %02x %02x %02x 
%02x",
++                              d->target_name, copy_size, out_data->maxsize,
++                              out_data->chunk->offset, out_data->chunk->size,
++                              out_data->chunk->stride,
++                              copy_size > 0 ? out_bytes[0] : 0,
++                              copy_size > 1 ? out_bytes[1] : 0,
++                              copy_size > 2 ? out_bytes[2] : 0,
++                              copy_size > 3 ? out_bytes[3] : 0,
++                              copy_size > 4 ? out_bytes[4] : 0,
++                              copy_size > 5 ? out_bytes[5] : 0,
++                              copy_size > 6 ? out_bytes[6] : 0,
++                              copy_size > 7 ? out_bytes[7] : 0);
++              d->logged_first_output_buffer = true;
++      }
++
++      pw_stream_queue_buffer(d->playback, out_buf);
++      pw_stream_queue_buffer(d->capture, in_buf);
++}
++
++static const struct pw_stream_events capture_events = {
++      PW_VERSION_STREAM_EVENTS,
++      .state_changed = capture_state_changed,
++      .process = capture_process,
++};
++
++static const struct pw_stream_events playback_events = {
++      PW_VERSION_STREAM_EVENTS,
++      .state_changed = playback_state_changed,
++};
++
++static void registry_global(void *data, uint32_t id, uint32_t permissions,
++              const char *type, uint32_t version, const struct spa_dict 
*props)
++{
++      struct forwarder_data *d = data;
++      const char *name;
++
++      if (!spa_streq(type, PW_TYPE_INTERFACE_Node) || props == NULL)
++              return;
++      name = spa_dict_lookup(props, PW_KEY_NODE_NAME);
++      if (!spa_streq(name, d->target_name))
++              return;
++      d->target_id = id;
++      pw_log_info("compress-forwarder target:%s resolved id=%u", 
d->target_name, id);
++}
++
++static void registry_global_remove(void *data, uint32_t id)
++{
++      struct forwarder_data *d = data;
++
++      if (id != d->target_id)
++              return;
++      pw_log_info("compress-forwarder target:%s removed id=%u", 
d->target_name, id);
++      d->target_id = PW_ID_ANY;
++      destroy_playback_stream(d);
++}
++
++static const struct pw_registry_events registry_events = {
++      PW_VERSION_REGISTRY_EVENTS,
++      .global = registry_global,
++      .global_remove = registry_global_remove,
++};
++
++static int create_playback_stream(struct forwarder_data *d)
++{
++      struct pw_properties *props;
++      const struct spa_pod *params[2];
++      uint8_t buffer[1024];
++      struct spa_pod_builder builder;
++      uint32_t n_params = 0;
++      int res;
++
++      props = pw_properties_new(PW_KEY_MEDIA_CLASS, "Stream/Output/Audio",
++                      PW_KEY_NODE_NAME, "compress-offload-forwarder-output",
++                      PW_KEY_MEDIA_TYPE, "Audio",
++                      PW_KEY_MEDIA_CATEGORY, "Playback",
++                      PW_KEY_MEDIA_ROLE, "Music",
++                      PW_KEY_TARGET_OBJECT, d->target_name,
++                      PW_KEY_NODE_AUTOCONNECT, "true",
++                      PW_KEY_STREAM_DONT_REMIX, "true",
++                      "node.dont-reconnect", "true",
++                      PW_KEY_NODE_RATE, "1/48000",
++                      "compress.offload", "true",
++                      "codec.type", d->codec,
++                      NULL);
++      if (props == NULL)
++              return -errno;
++      pw_properties_setf(props, "codec.sample_rate", "%u", d->rate);
++      pw_properties_setf(props, "codec.channels", "%u", d->channels);
++      pw_properties_setf(props, "codec.bit_rate", "%u", d->bitrate);
++      pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", d->rate);
++
++      d->playback = pw_stream_new(d->core, "compress offload forwarder 
output", props);
++      if (d->playback == NULL)
++              return -errno;
++      pw_stream_add_listener(d->playback, &d->playback_listener, 
&playback_events, d);
++
++      spa_pod_builder_init(&builder, buffer, sizeof(buffer));
++      params[n_params++] = build_encoded_format(d, &builder, 
SPA_PARAM_EnumFormat);
++      pw_log_debug("compress-forwarder target:%s connect target-id=%u", 
d->target_name, d->target_id);
++      res = pw_stream_connect(d->playback, PW_DIRECTION_OUTPUT, d->target_id,
++                      PW_STREAM_FLAG_AUTOCONNECT |
++                      PW_STREAM_FLAG_NO_CONVERT |
++                      PW_STREAM_FLAG_MAP_BUFFERS |
++                      PW_STREAM_FLAG_RT_PROCESS,
++                      params, n_params);
++      if (res < 0) {
++              destroy_playback_stream(d);
++              return res;
++      }
++      pw_stream_set_active(d->playback, false);
++      d->playback_active = false;
++      d->playback_failed = false;
++      return 0;
++}
++
++static int create_capture_stream(struct forwarder_data *d)
++{
++      struct pw_properties *props;
++      const struct spa_pod *params[2];
++      uint8_t buffer[1024];
++      struct spa_pod_builder builder;
++      uint32_t n_params = 0;
++      int res;
++
++      props = pw_properties_new(PW_KEY_MEDIA_CLASS, "Stream/Input/Audio",
++                      PW_KEY_NODE_NAME, "compress-offload-forwarder-capture",
++                      PW_KEY_TARGET_OBJECT, d->source_name,
++                      PW_KEY_NODE_AUTOCONNECT, "true",
++                      PW_KEY_STREAM_CAPTURE_SINK, "true",
++                      PW_KEY_STREAM_DONT_REMIX, "true",
++                      "node.dont-reconnect", "true",
++                      NULL);
++      if (props == NULL)
++              return -errno;
++      pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", d->rate);
++
++      d->capture = pw_stream_new(d->core, "compress offload forwarder 
capture", props);
++      if (d->capture == NULL)
++              return -errno;
++      pw_stream_add_listener(d->capture, &d->capture_listener, 
&capture_events, d);
++
++      spa_pod_builder_init(&builder, buffer, sizeof(buffer));
++      params[n_params++] = build_raw_format(d, &builder, 
SPA_PARAM_EnumFormat);
++      res = pw_stream_connect(d->capture, PW_DIRECTION_INPUT, PW_ID_ANY,
++                      PW_STREAM_FLAG_AUTOCONNECT |
++                      PW_STREAM_FLAG_MAP_BUFFERS |
++                      PW_STREAM_FLAG_RT_PROCESS,
++                      params, n_params);
++      if (res < 0)
++              return res;
++      pw_stream_set_active(d->capture, true);
++      return 0;
++}
++
++static void core_error(void *data, uint32_t id, int seq, int res, const char 
*message)
++{
++      struct module *module = data;
++
++      pw_log_error("compress-forwarder error id:%u seq:%d res:%d (%s): %s",
++                      id, seq, res, spa_strerror(res), message);
++      if (id == PW_ID_CORE && res == -EPIPE)
++              module_schedule_unload(module);
++}
++
++static const struct pw_core_events core_events = {
++      PW_VERSION_CORE_EVENTS,
++      .error = core_error,
++};
++
++static int module_compress_offload_forwarder_load(struct module *module)
++{
++      struct forwarder_data *d = module->user_data;
++      int res;
++
++      PW_LOG_TOPIC_INIT(mod_topic);
++      d->loop = pw_context_get_main_loop(module->impl->context);
++      if (d->loop == NULL)
++              return -EINVAL;
++      d->activate_event = pw_loop_add_event(d->loop, playback_activate_event, 
d);
++      if (d->activate_event == NULL)
++              return -errno;
++      d->core = pw_context_connect(module->impl->context, NULL, 0);
++      if (d->core == NULL)
++              return -errno;
++      pw_core_add_listener(d->core, &d->core_listener, &core_events, module);
++      d->target_id = PW_ID_ANY;
++      d->registry = pw_core_get_registry(d->core, PW_VERSION_REGISTRY, 0);
++      if (d->registry == NULL)
++              return -errno;
++      pw_registry_add_listener(d->registry, &d->registry_listener, 
&registry_events, d);
++
++      res = create_capture_stream(d);
++      if (res < 0)
++              return res;
++
++      pw_log_info("compress-forwarder loaded: source=%s monitor=%s target=%s 
codec=%s rate=%u channels=%u",
++                      d->source_name, d->monitor_name, d->target_name,
++                      d->codec, d->rate, d->channels);
++      module_emit_loaded(module, 0);
++      return 0;
++}
++
++static int module_compress_offload_forwarder_unload(struct module *module)
++{
++      struct forwarder_data *d = module->user_data;
++
++      if (d->capture != NULL) {
++              spa_hook_remove(&d->capture_listener);
++              pw_stream_destroy(d->capture);
++              d->capture = NULL;
++      }
++      destroy_playback_stream(d);
++      if (d->registry != NULL) {
++              spa_hook_remove(&d->registry_listener);
++              pw_proxy_destroy((struct pw_proxy*)d->registry);
++              d->registry = NULL;
++      }
++      if (d->core != NULL) {
++              spa_hook_remove(&d->core_listener);
++              pw_core_disconnect(d->core);
++              d->core = NULL;
++      }
++      if (d->activate_event != NULL) {
++              pw_loop_destroy_source(d->loop, d->activate_event);
++              d->activate_event = NULL;
++      }
++      d->loop = NULL;
++      return 0;
++}
++
++static int module_compress_offload_forwarder_prepare(struct module *module)
++{
++      struct forwarder_data *d = module->user_data;
++      struct pw_properties *props = module->props;
++      const char *str;
++
++      PW_LOG_TOPIC_INIT(mod_topic);
++      str = pw_properties_get(props, "source_sink");
++      spa_scnprintf(d->source_name, sizeof(d->source_name), "%s",
++                      str ? str : "pal_speaker_compress");
++      str = pw_properties_get(props, "target_sink");
++      spa_scnprintf(d->target_name, sizeof(d->target_name), "%s",
++                      str ? str : "pal_sink_speaker_compress");
++      str = pw_properties_get(props, "codec");
++      spa_scnprintf(d->codec, sizeof(d->codec), "%s", str ? str : 
DEFAULT_CODEC);
++      d->rate = pw_properties_get_uint32(props, "rate", DEFAULT_RATE);
++      d->channels = pw_properties_get_uint32(props, "channels", 
DEFAULT_CHANNELS);
++      d->bitrate = pw_properties_get_uint32(props, "bitrate", 
DEFAULT_BITRATE);
++      d->block_size = pw_properties_get_uint32(props, "block_size", 
DEFAULT_BLOCK_SIZE);
++      spa_scnprintf(d->monitor_name, sizeof(d->monitor_name), "%s.monitor", 
d->source_name);
++
++      if (!spa_streq(d->codec, "mp3")) {
++              pw_log_warn("compress-forwarder only supports mp3 right now, 
requested codec=%s", d->codec);
++              return -ENOTSUP;
++      }
++      if (d->channels == 0)
++              d->channels = DEFAULT_CHANNELS;
++      if (d->rate == 0)
++              d->rate = DEFAULT_RATE;
++      if (d->block_size == 0)
++              d->block_size = DEFAULT_BLOCK_SIZE;
++
++      return 0;
++}
++
++static const struct spa_dict_item module_compress_offload_forwarder_info[] = {
++      { PW_KEY_MODULE_AUTHOR,      "Qualcomm Technologies, Inc." },
++      { PW_KEY_MODULE_DESCRIPTION, "Forward Pulse compress-offload sink data 
to PAL compress sink" },
++      { PW_KEY_MODULE_USAGE,       forwarder_options },
++      { PW_KEY_MODULE_VERSION,     PACKAGE_VERSION },
++};
++
++DEFINE_MODULE_INFO(module_compress_offload_forwarder) = {
++      .name       = "module-compress-offload-forwarder",
++      .prepare    = module_compress_offload_forwarder_prepare,
++      .load       = module_compress_offload_forwarder_load,
++      .unload     = module_compress_offload_forwarder_unload,
++      .properties = 
&SPA_DICT_INIT_ARRAY(module_compress_offload_forwarder_info),
++      .data_size  = sizeof(struct forwarder_data),
++};
+diff --git 
a/src/modules/module-protocol-pulse/modules/module-compress-offload-sink.c 
b/src/modules/module-protocol-pulse/modules/module-compress-offload-sink.c
+new file mode 100644
+index 0000000..243cc8e
+--- /dev/null
++++ b/src/modules/module-protocol-pulse/modules/module-compress-offload-sink.c
+@@ -0,0 +1,293 @@
++/* PipeWire */
++/* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */
++/* SPDX-License-Identifier: BSD-3-Clause-Clear */
++
++#include <errno.h>
++#include <stdlib.h>
++
++#include <spa/utils/result.h>
++#include <pipewire/pipewire.h>
++
++#include "../module.h"
++
++static const char *const pulse_module_options =
++      "sink_name=<name of sink> "
++      "sink_properties=<properties for the sink> "
++      "target_sink=<node.name of the downstream compress-offload sink> "
++      "codec=<codec name, default mp3> "
++      "rate=<sample rate, default 44100> "
++      "channels=<number of channels, default 2> "
++      "bitrate=<bit rate, default 128000>";
++
++#define NAME "compress-offload-sink"
++
++PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
++#define PW_LOG_TOPIC_DEFAULT mod_topic
++
++struct module_compress_offload_sink_data {
++      struct pw_core *core;
++      struct spa_hook core_listener;
++
++      struct pw_proxy *proxy;
++      struct spa_hook proxy_listener;
++};
++
++static void module_proxy_removed(void *data)
++{
++      struct module *module = data;
++      struct module_compress_offload_sink_data *d = module->user_data;
++
++      pw_log_debug("compress-forwarder proxy removed: proxy=%p", d->proxy);
++      pw_proxy_destroy(d->proxy);
++}
++
++static void module_proxy_destroy(void *data)
++{
++      struct module *module = data;
++      struct module_compress_offload_sink_data *d = module->user_data;
++
++      pw_log_debug("compress-forwarder proxy destroy: proxy=%p - scheduling 
module unload", d->proxy);
++      spa_hook_remove(&d->proxy_listener);
++      d->proxy = NULL;
++      module_schedule_unload(module);
++}
++
++static void module_proxy_bound_props(void *data, uint32_t global_id, const 
struct spa_dict *props)
++{
++      struct module *module = data;
++      struct module_compress_offload_sink_data *d = module->user_data;
++
++      pw_log_debug("proxy %p bound id:%u module_index=%u", d->proxy, 
global_id, module->index);
++      pw_log_debug("compress-forwarder node successfully created and bound, 
emitting loaded");
++      module_emit_loaded(module, 0);
++}
++
++static void module_proxy_error(void *data, int seq, int res, const char 
*message)
++{
++      struct module *module = data;
++      struct module_compress_offload_sink_data *d = module->user_data;
++
++      pw_log_error("proxy %p error %d: %s", d->proxy, res, message);
++      pw_proxy_destroy(d->proxy);
++}
++
++static const struct pw_proxy_events proxy_events = {
++      PW_VERSION_PROXY_EVENTS,
++      .removed      = module_proxy_removed,
++      .bound_props  = module_proxy_bound_props,
++      .error        = module_proxy_error,
++      .destroy      = module_proxy_destroy,
++};
++
++static void module_core_error(void *data, uint32_t id, int seq, int res, 
const char *message)
++{
++      struct module *module = data;
++
++      pw_log_error("error id:%u seq:%d res:%d (%s): %s",
++                      id, seq, res, spa_strerror(res), message);
++
++      if (id == PW_ID_CORE && res == -EPIPE)
++              module_schedule_unload(module);
++}
++
++static const struct pw_core_events core_events = {
++      PW_VERSION_CORE_EVENTS,
++      .error = module_core_error,
++};
++
++static int module_compress_offload_sink_load(struct module *module)
++{
++      struct module_compress_offload_sink_data *d = module->user_data;
++
++      pw_log_info("compress-forwarder load: node=%s target=%s codec=%s 
rate=%s channels=%s bitrate=%s",
++                      pw_properties_get(module->props, PW_KEY_NODE_NAME),
++                      pw_properties_get(module->props, 
"compress.target.object"),
++                      pw_properties_get(module->props, "codec.type"),
++                      pw_properties_get(module->props, "codec.sample_rate"),
++                      pw_properties_get(module->props, "codec.channels"),
++                      pw_properties_get(module->props, "codec.bit_rate"));
++
++      d->core = pw_context_connect(module->impl->context, NULL, 0);
++      if (d->core == NULL) {
++              pw_log_error("compress-forwarder core connect failed: errno=%d 
(%s)",
++                              errno, spa_strerror(-errno));
++              return -errno;
++      }
++
++      pw_log_debug("compress-forwarder core connected: core=%p", d->core);
++      pw_core_add_listener(d->core, &d->core_listener, &core_events, module);
++      pw_properties_setf(module->props, "pulse.module.id", "%u", 
module->index);
++
++      d->proxy = pw_core_create_object(d->core,
++                                       "adapter", PW_TYPE_INTERFACE_Node, 
PW_VERSION_NODE,
++                                       module->props ? &module->props->dict : 
NULL, 0);
++      if (d->proxy == NULL) {
++              pw_log_error("compress-forwarder adapter create failed: 
errno=%d (%s)",
++                              errno, spa_strerror(-errno));
++              return -errno;
++      }
++
++      pw_log_debug("compress-forwarder adapter create requested: proxy=%p - 
waiting for bound_props", d->proxy);
++      pw_proxy_add_listener(d->proxy, &d->proxy_listener, &proxy_events, 
module);
++      return SPA_RESULT_RETURN_ASYNC(0);
++}
++
++static int module_compress_offload_sink_unload(struct module *module)
++{
++      struct module_compress_offload_sink_data *d = module->user_data;
++
++      pw_log_debug("compress-forwarder unload: proxy=%p core=%p - cleaning 
up", d->proxy, d->core);
++      if (d->proxy != NULL) {
++              spa_hook_remove(&d->proxy_listener);
++              pw_proxy_destroy(d->proxy);
++              d->proxy = NULL;
++      }
++      if (d->core != NULL) {
++              spa_hook_remove(&d->core_listener);
++              pw_core_disconnect(d->core);
++              d->core = NULL;
++      }
++      return 0;
++}
++
++static int module_compress_offload_sink_prepare(struct module * const module)
++{
++      struct pw_properties * const props = module->props;
++      const char *str;
++      uint32_t rate = 44100;
++      uint32_t channels = 2;
++      uint32_t bitrate = 128000;
++
++      PW_LOG_TOPIC_INIT(mod_topic);
++
++      if ((str = pw_properties_get(props, "sink_name")) != NULL) {
++              pw_log_debug("compress-forwarder option: sink_name=%s", str);
++              pw_properties_set(props, PW_KEY_NODE_NAME, str);
++              pw_properties_set(props, "sink_name", NULL);
++      } else {
++              pw_log_debug("compress-forwarder option: sink_name missing, 
using default");
++              pw_properties_set(props, PW_KEY_NODE_NAME, 
"compress-offload-sink");
++      }
++
++      if ((str = pw_properties_get(props, "sink_properties")) != NULL) {
++              pw_log_debug("compress-forwarder option: sink_properties=%s", 
str);
++              module_args_add_props(props, str);
++              pw_properties_set(props, "sink_properties", NULL);
++      }
++
++      if ((str = pw_properties_get(props, "target_sink")) != NULL) {
++              pw_log_debug("compress-forwarder option: target_sink=%s", str);
++              pw_properties_set(props, "compress.target.object", str);
++              pw_properties_set(props, "target_sink", NULL);
++      } else {
++              pw_log_warn("compress-forwarder option: target_sink missing; 
stream will not have explicit compress target");
++      }
++
++      if ((str = pw_properties_get(props, "rate")) != NULL) {
++              uint32_t v = (uint32_t)atoi(str);
++              if (v > 0)
++                      rate = v;
++              pw_properties_set(props, "rate", NULL);
++      }
++      if ((str = pw_properties_get(props, "channels")) != NULL) {
++              uint32_t v = (uint32_t)atoi(str);
++              if (v > 0)
++                      channels = v;
++              pw_properties_set(props, "channels", NULL);
++      }
++      if ((str = pw_properties_get(props, "bitrate")) != NULL) {
++              uint32_t v = (uint32_t)atoi(str);
++              if (v > 0)
++                      bitrate = v;
++              pw_properties_set(props, "bitrate", NULL);
++      }
++      if ((str = pw_properties_get(props, "channel_map")) != NULL) {
++              pw_properties_set(props, "audio.position", str);
++              pw_properties_set(props, "channel_map", NULL);
++      } else {
++              switch (channels) {
++              case 1:
++                      pw_properties_set(props, "audio.position", "[ MONO ]");
++                      break;
++              case 2:
++                      pw_properties_set(props, "audio.position", "[ FL FR ]");
++                      break;
++              case 3:
++                      pw_properties_set(props, "audio.position", "[ FL FR LFE 
]");
++                      break;
++              case 4:
++                      pw_properties_set(props, "audio.position", "[ FL FR RL 
RR ]");
++                      break;
++              case 5:
++                      pw_properties_set(props, "audio.position", "[ FL FR FC 
RL RR ]");
++                      break;
++              case 6:
++                      pw_properties_set(props, "audio.position", "[ FL FR FC 
LFE RL RR ]");
++                      break;
++              case 8:
++                      pw_properties_set(props, "audio.position", "[ FL FR FC 
LFE RL RR SL SR ]");
++                      break;
++              default:
++                      break;
++              }
++      }
++
++      str = pw_properties_get(props, "codec");
++      pw_properties_set(props, "codec.type", str ? str : "mp3");
++      pw_properties_setf(props, "codec.sample_rate", "%u", rate);
++      pw_properties_setf(props, "codec.channels",    "%u", channels);
++      pw_properties_setf(props, "codec.bit_rate",    "%u", bitrate);
++      pw_properties_set(props, "codec", NULL);
++
++      if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL)
++              pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
++      if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL)
++              pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "%s 
(compress offload)",
++                              pw_properties_get(props, PW_KEY_NODE_NAME));
++
++      /*
++       * Advertise this node as an encoded-only sink so WirePlumber's
++       * canPassthrough() allows linking compress streams to it.
++       * audio.format=S16LE is kept as the synthetic PCM sample_spec that
++       * Pulse clients see in pactl; no PCM conversion ever occurs.
++       */
++      pw_properties_set(props, PW_KEY_AUDIO_FORMAT,                "S16LE");
++      pw_properties_setf(props, PW_KEY_AUDIO_RATE,                 "%u", 
rate);
++      pw_properties_setf(props, PW_KEY_AUDIO_CHANNELS,             "%u", 
channels);
++      pw_properties_set(props, "item.node.supports-encoded-fmts",  "true");
++      pw_properties_set(props, PW_KEY_NODE_VIRTUAL,                "true");
++      pw_properties_set(props, PW_KEY_PRIORITY_SESSION,            "1");
++      pw_properties_set(props, PW_KEY_PRIORITY_DRIVER,             "1");
++      pw_properties_set(props, "monitor.channel-volumes",          "true");
++      pw_properties_set(props, "monitor.passthrough",              "true");
++      pw_properties_set(props, "compress.offload",                 "true");
++      pw_properties_set(props, PW_KEY_FACTORY_NAME,                
"support.null-audio-sink");
++
++      pw_log_info("prepared compress-forwarder alias: name=%s target=%s 
codec=%s rate=%u channels=%u bitrate=%u media.class=%s audio.format=%s 
virtual=%s factory=%s",
++                      pw_properties_get(props, PW_KEY_NODE_NAME),
++                      pw_properties_get(props, "compress.target.object"),
++                      pw_properties_get(props, "codec.type"),
++                      rate, channels, bitrate,
++                      pw_properties_get(props, PW_KEY_MEDIA_CLASS),
++                      pw_properties_get(props, PW_KEY_AUDIO_FORMAT),
++                      pw_properties_get(props, PW_KEY_NODE_VIRTUAL),
++                      pw_properties_get(props, PW_KEY_FACTORY_NAME));
++
++      return 0;
++}
++
++static const struct spa_dict_item module_compress_offload_sink_info[] = {
++      { PW_KEY_MODULE_AUTHOR,      "Qualcomm Technologies, Inc." },
++      { PW_KEY_MODULE_DESCRIPTION, "Pulse-visible alias sink for compressed 
offload playback" },
++      { PW_KEY_MODULE_USAGE,       pulse_module_options },
++      { PW_KEY_MODULE_VERSION,     PACKAGE_VERSION },
++};
++
++DEFINE_MODULE_INFO(module_compress_offload_sink) = {
++      .name       = "module-compress-offload-sink",
++      .prepare    = module_compress_offload_sink_prepare,
++      .load       = module_compress_offload_sink_load,
++      .unload     = module_compress_offload_sink_unload,
++      .properties = &SPA_DICT_INIT_ARRAY(module_compress_offload_sink_info),
++      .data_size  = sizeof(struct module_compress_offload_sink_data),
++};
+diff --git a/src/modules/module-protocol-pulse/pulse-server.c 
b/src/modules/module-protocol-pulse/pulse-server.c
+index eb3233b..61f0dfb 100644
+--- a/src/modules/module-protocol-pulse/pulse-server.c
++++ b/src/modules/module-protocol-pulse/pulse-server.c
+@@ -84,6 +84,9 @@ struct temporary_move_data {
+       uint8_t used:1;
+ };
+
++/* Buffer size used for compress-offload streams (frame_size == 1). */
++#define COMPRESS_OFFLOAD_BUF_SIZE  16484u
++
+ static struct sample *find_sample(struct impl *impl, uint32_t index, const 
char *name)
+ {
+       union pw_map_item *item;
+@@ -1199,10 +1202,18 @@ static const struct spa_pod *get_buffers_param(struct 
stream *s,
+       blocks = 1;
+       stride = s->frame_size;
+
++      /* compressed offload uses byte granularity */
++      if (s->frame_size == 1) {
++              size = COMPRESS_OFFLOAD_BUF_SIZE;
++              pw_log_debug("[%s] get_buffers_param: compress offload path 
size=%u", s->client->name, size);
++              goto build_buffers_param;
++      }
++
+       size = defs->quantum_limit * s->frame_size;
+
+       pw_log_info("[%s] stride %d size %u", s->client->name, stride, size);
+
++build_buffers_param:
+       param = spa_pod_builder_add_object(b,
+                       SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
+                       SPA_PARAM_BUFFERS_buffers, 
SPA_POD_CHOICE_RANGE_Int(MIN_BUFFERS,
+@@ -1228,6 +1239,23 @@ static void stream_param_changed(void *data, uint32_t 
id, const struct spa_pod *
+       if (id != SPA_PARAM_Format || param == NULL)
+               return;
+
++      /* For compress-offload streams, force byte-granularity regardless
++       * of negotiated format (we connect with S16LE but data is compressed). 
*/
++      {
++              const char *co = stream->props ?
++                      pw_properties_get(stream->props, "compress.offload") : 
NULL;
++              if (co && pw_properties_parse_bool(co)) {
++                      uint32_t media_type = 0, media_subtype = 0;
++                      spa_format_parse(param, &media_type, &media_subtype);
++                      stream->frame_size = 1;
++                      stream->rate = stream->ss.rate ? stream->ss.rate :
++                              pw_properties_get_uint32(stream->props, 
"codec.sample_rate", 44100);
++                      pw_log_debug("[%s] compress-offload 
stream_param_changed: forcing frame_size=1 rate=%u subtype=%u",
++                              stream->client->name, stream->rate, 
media_subtype);
++                      goto stream_param_format_done;
++              }
++      }
++
+       if ((res = format_parse_param(param, false, &stream->ss, &stream->map, 
NULL, NULL)) < 0) {
+               pw_stream_set_error(stream->stream, res, "format not 
supported");
+               return;
+@@ -1243,6 +1271,9 @@ static void stream_param_changed(void *data, uint32_t 
id, const struct spa_pod *
+               return;
+       }
+       stream->rate = stream->ss.rate;
++stream_param_format_done:
++      pw_log_debug("[%s] stream param done: frame_size=%u rate=%u 
ss.format=%u",
++                      stream->client->name, stream->frame_size, stream->rate, 
stream->ss.format);
+
+       if (stream->create_tag != SPA_ID_INVALID) {
+               struct pw_manager_object *peer;
+@@ -1583,6 +1614,98 @@ static void log_format_info(struct impl *impl, enum 
spa_log_level level, struct
+                               impl, it->key, it->value);
+ }
+
++static const struct spa_pod *build_compress_offload_format(struct 
spa_pod_builder *b,
++              uint32_t id, const struct pw_properties *props, uint32_t *rate)
++{
++      const char *codec;
++      struct spa_audio_info info;
++      uint32_t sample_rate, channels, bitrate;
++
++      spa_zero(info);
++      info.media_type = SPA_MEDIA_TYPE_audio;
++      codec = pw_properties_get(props, "codec.type");
++      sample_rate = pw_properties_get_uint32(props, "codec.sample_rate", 
44100);
++      channels = pw_properties_get_uint32(props, "codec.channels", 2);
++      bitrate = pw_properties_get_uint32(props, "codec.bit_rate", 128000);
++
++      if (codec == NULL || spa_streq(codec, "mp3")) {
++              info.media_subtype = SPA_MEDIA_SUBTYPE_mp3;
++              info.info.mp3.rate = sample_rate;
++              info.info.mp3.channels = channels;
++              info.info.mp3.channel_mode = channels == 1 ?
++                      SPA_AUDIO_MP3_CHANNEL_MODE_MONO : 
SPA_AUDIO_MP3_CHANNEL_MODE_STEREO;
++      } else if (spa_streq(codec, "aac") || spa_streq(codec, "aac_adts")) {
++              info.media_subtype = SPA_MEDIA_SUBTYPE_aac;
++              info.info.aac.rate = sample_rate;
++              info.info.aac.channels = channels;
++              info.info.aac.bitrate = bitrate;
++              info.info.aac.stream_format = 
SPA_AUDIO_AAC_STREAM_FORMAT_MP4ADTS;
++      } else if (spa_streq(codec, "aac_adif")) {
++              info.media_subtype = SPA_MEDIA_SUBTYPE_aac;
++              info.info.aac.rate = sample_rate;
++              info.info.aac.channels = channels;
++              info.info.aac.bitrate = bitrate;
++              info.info.aac.stream_format = SPA_AUDIO_AAC_STREAM_FORMAT_ADIF;
++      } else if (spa_streq(codec, "aac_latm")) {
++              info.media_subtype = SPA_MEDIA_SUBTYPE_aac;
++              info.info.aac.rate = sample_rate;
++              info.info.aac.channels = channels;
++              info.info.aac.bitrate = bitrate;
++              info.info.aac.stream_format = 
SPA_AUDIO_AAC_STREAM_FORMAT_MP4LATM;
++      } else if (spa_streq(codec, "flac")) {
++              info.media_subtype = SPA_MEDIA_SUBTYPE_flac;
++              info.info.flac.rate = sample_rate;
++              info.info.flac.channels = channels;
++      } else if (spa_streq(codec, "vorbis")) {
++              info.media_subtype = SPA_MEDIA_SUBTYPE_vorbis;
++              info.info.vorbis.rate = sample_rate;
++              info.info.vorbis.channels = channels;
++      } else if (spa_streq(codec, "opus")) {
++              info.media_subtype = SPA_MEDIA_SUBTYPE_opus;
++              info.info.opus.rate = sample_rate;
++              info.info.opus.channels = channels;
++      } else if (spa_streq(codec, "wma") || spa_streq(codec, "wma_std")) {
++              info.media_subtype = SPA_MEDIA_SUBTYPE_wma;
++              info.info.wma.rate = sample_rate;
++              info.info.wma.channels = channels;
++              info.info.wma.bitrate = bitrate;
++              info.info.wma.profile = SPA_AUDIO_WMA_PROFILE_WMA9;
++      } else if (spa_streq(codec, "wma_pro")) {
++              info.media_subtype = SPA_MEDIA_SUBTYPE_wma;
++              info.info.wma.rate = sample_rate;
++              info.info.wma.channels = channels;
++              info.info.wma.bitrate = bitrate;
++              info.info.wma.profile = SPA_AUDIO_WMA_PROFILE_WMA9_PRO;
++      } else {
++              errno = ENOTSUP;
++              return NULL;
++      }
++      if (rate != NULL)
++              *rate = sample_rate;
++      return spa_format_audio_build(b, id, &info);
++}
++
++static void synthesize_compress_offload_spec(struct sample_spec *ss,
++              struct channel_map *map, const struct pw_properties *props)
++{
++      uint32_t channels;
++      ss->format = SPA_AUDIO_FORMAT_S16_LE;
++      ss->rate = pw_properties_get_uint32(props, "codec.sample_rate", 44100);
++      channels = pw_properties_get_uint32(props, "codec.channels", 2);
++      ss->channels = (uint8_t)(channels > 0 ? channels : 2);
++      spa_zero(*map);
++      switch (ss->channels) {
++      case 1: channel_map_parse("mono", map); break;
++      case 2: channel_map_parse("stereo", map); break;
++      case 3: channel_map_parse("surround-21", map); break;
++      case 4: channel_map_parse("surround-40", map); break;
++      case 5: channel_map_parse("surround-50", map); break;
++      case 6: channel_map_parse("surround-51", map); break;
++      case 8: channel_map_parse("surround-71", map); break;
++      default: channel_map_parse("stereo", map); ss->channels = 2; break;
++      }
++}
++
+ static int do_create_playback_stream(struct client *client, uint32_t command, 
uint32_t tag, struct message *m)
+ {
+       struct impl *impl = client->impl;
+@@ -1609,7 +1732,8 @@ static int do_create_playback_stream(struct client 
*client, uint32_t command, ui
+               muted_set = false,
+               fail_on_suspend = false,
+               relative_volume = false,
+-              passthrough = false;
++              passthrough = false,
++              compress_offload = false;
+       struct volume volume;
+       struct pw_properties *props = NULL;
+       uint8_t n_formats = 0;
+@@ -1668,6 +1792,68 @@ static int do_create_playback_stream(struct client 
*client, uint32_t command, ui
+       }
+       o = find_device(client, sink_index, sink_name, true, &is_monitor);
+
++      if (o != NULL) {
++              const struct pw_node_info *ninfo = o->info;
++              const struct spa_dict *sp = (ninfo && ninfo->props) ? 
ninfo->props : NULL;
++              char node_name_buf[256] = {0};
++              const char *node_name;
++              const char *co;
++              const char *_nn = pw_properties_get(o->props, PW_KEY_NODE_NAME);
++
++              if (_nn)
++                      snprintf(node_name_buf, sizeof(node_name_buf), "%s", 
_nn);
++              node_name = node_name_buf[0] ? node_name_buf : NULL;
++              co = sp ? spa_dict_lookup(sp, "compress.offload") : NULL;
++              pw_log_debug("[%s] compress-offload check: node=%s ninfo=%p 
co=%s",
++                              client->name, node_name ? node_name : "(null)", 
ninfo, co ? co : "(null)");
++              if (co && spa_atob(co)) {
++                      char target_buf[256] = {0};
++                      char codec_buf[64] = {0};
++                      char rate_buf[32] = {0};
++                      char channels_buf[16] = {0};
++                      char bitrate_buf[32] = {0};
++                      const char *target;
++                      const char *codec;
++                      const char *rate_s;
++                      const char *channels;
++                      const char *bitrate;
++                      const char *_s;
++
++                      if ((_s = spa_dict_lookup(sp, 
"compress.target.object")) != NULL)
++                              snprintf(target_buf, sizeof(target_buf), "%s", 
_s);
++                      if ((_s = spa_dict_lookup(sp, "codec.type")) != NULL)
++                              snprintf(codec_buf, sizeof(codec_buf), "%s", 
_s);
++                      if ((_s = spa_dict_lookup(sp, "codec.sample_rate")) != 
NULL)
++                              snprintf(rate_buf, sizeof(rate_buf), "%s", _s);
++                      if ((_s = spa_dict_lookup(sp, "codec.channels")) != 
NULL)
++                              snprintf(channels_buf, sizeof(channels_buf), 
"%s", _s);
++                      if ((_s = spa_dict_lookup(sp, "codec.bit_rate")) != 
NULL)
++                              snprintf(bitrate_buf, sizeof(bitrate_buf), 
"%s", _s);
++
++                      target = target_buf[0] ? target_buf : NULL;
++                      codec = codec_buf[0] ? codec_buf : NULL;
++                      rate_s = rate_buf[0] ? rate_buf : NULL;
++                      channels = channels_buf[0] ? channels_buf : NULL;
++                      bitrate = bitrate_buf[0] ? bitrate_buf : NULL;
++
++                      pw_properties_set(props, "compress.offload", "true");
++                      pw_properties_set(props, 
"item.node.supports-encoded-fmts", "true");
++                      pw_properties_set(props, PW_KEY_MEDIA_TYPE, "Audio");
++                      pw_properties_set(props, PW_KEY_MEDIA_CATEGORY, 
"Playback");
++                      pw_properties_set(props, PW_KEY_MEDIA_ROLE, "Music");
++                      if (codec) pw_properties_set(props, "codec.type", 
codec);
++                      if (rate_s) pw_properties_set(props, 
"codec.sample_rate", rate_s);
++                      if (channels) pw_properties_set(props, 
"codec.channels", channels);
++                      if (bitrate) pw_properties_set(props, "codec.bit_rate", 
bitrate);
++
++                      pw_log_info("[%s] detected compress-offload sink=%s 
target=%s codec=%s",
++                                      client->name, node_name ? node_name : 
"(null)",
++                                      target ? target : "(null)", codec ? 
codec : "(null)");
++                      pw_log_debug("[%s] compress target=%s - leaving 
sink_name=%s for virtual sink routing",
++                                      client->name, target ? target : 
"(null)", sink_name ? sink_name : "(null)");
++              }
++      }
++
+       spa_zero(fix_ss);
+       spa_zero(fix_map);
+       if ((fix_format || fix_rate || fix_channels) && o != NULL) {
+@@ -1715,6 +1901,9 @@ static int do_create_playback_stream(struct client 
*client, uint32_t command, ui
+                       goto error_protocol;
+       }
+
++      compress_offload = pw_properties_parse_bool(
++                      pw_properties_get(props, "compress.offload"));
++
+       if (client->version >= 21) {
+               if (message_get(m,
+                               TAG_U8, &n_formats,
+@@ -1732,7 +1921,9 @@ static int do_create_playback_stream(struct client 
*client, uint32_t command, ui
+                                               TAG_INVALID) < 0)
+                                       goto error_protocol;
+
+-                              if (n_params < MAX_FORMATS &&
++                              if (compress_offload) {
++                                      log_format_info(impl, 
SPA_LOG_LEVEL_DEBUG, &format);
++                              } else if (n_params < MAX_FORMATS &&
+                                   (params[n_params] = 
format_info_build_param(&b,
+                                               SPA_PARAM_EnumFormat, &format, 
&r)) != NULL) {
+                                       n_params++;
+@@ -1746,7 +1937,23 @@ static int do_create_playback_stream(struct client 
*client, uint32_t command, ui
+                       }
+               }
+       }
+-      if (sample_spec_valid(&ss)) {
++      if (compress_offload) {
++              uint32_t compress_rate = 0;
++              if (n_params < MAX_FORMATS &&
++                  (params[n_params] = build_compress_offload_format(&b,
++                                      SPA_PARAM_EnumFormat, props, 
&compress_rate)) != NULL) {
++                      n_params++;
++                      n_valid_formats++;
++                      ss_rate = rate = compress_rate;
++                      synthesize_compress_offload_spec(&ss, &map, props);
++                      pw_log_debug("[%s] synthesized compress-offload format 
codec=%s rate=%u channels=%u",
++                                      client->name, pw_properties_get(props, 
"codec.type"), compress_rate,
++                                      pw_properties_get_uint32(props, 
"codec.channels", 2));
++              } else {
++                      pw_log_warn("%p: unsupported compress codec:%s",
++                                      impl, pw_properties_get(props, 
"codec.type"));
++              }
++      } else if (sample_spec_valid(&ss)) {
+               struct sample_spec sfix = ss;
+               struct channel_map mfix = map;
+
+@@ -1770,6 +1977,8 @@ static int do_create_playback_stream(struct client 
*client, uint32_t command, ui
+       if (m->offset != m->length)
+               goto error_protocol;
+
++      pw_log_debug("[%s] format check: n_valid=%u n_params=%u compress=%d 
passthrough=%d",
++                      client->name, n_valid_formats, n_params, 
compress_offload, passthrough);
+       if (n_valid_formats == 0)
+               goto error_no_formats;
+
+@@ -1806,6 +2015,8 @@ static int do_create_playback_stream(struct client 
*client, uint32_t command, ui
+       flags = 0;
+       if (no_move)
+               flags |= PW_STREAM_FLAG_DONT_RECONNECT;
++      if (compress_offload)
++              flags |= PW_STREAM_FLAG_NO_CONVERT;
+
+       if (sink_name != NULL) {
+               if (o != NULL)
+@@ -1833,14 +2044,35 @@ static int do_create_playback_stream(struct client 
*client, uint32_t command, ui
+                       &stream->stream_listener,
+                       &stream_events, stream);
+
+-      pw_stream_connect(stream->stream,
+-                      PW_DIRECTION_OUTPUT,
+-                      SPA_ID_INVALID,
+-                      flags |
+-                      PW_STREAM_FLAG_AUTOCONNECT |
+-                      PW_STREAM_FLAG_RT_PROCESS |
+-                      PW_STREAM_FLAG_MAP_BUFFERS,
+-                      params, n_params);
++      if (compress_offload) {
++              uint8_t raw_buf[256];
++              struct spa_pod_builder rb = SPA_POD_BUILDER_INIT(raw_buf, 
sizeof(raw_buf));
++              struct spa_audio_info_raw raw_info = {
++                      .format = SPA_AUDIO_FORMAT_S16_LE,
++                      .rate = ss.rate ? ss.rate : 44100,
++                      .channels = ss.channels ? ss.channels : 2,
++              };
++              const struct spa_pod *raw_params[1];
++              raw_params[0] = spa_format_audio_raw_build(&rb, 
SPA_PARAM_EnumFormat, &raw_info);
++              pw_log_debug("[%s] pw_stream_connect: using raw S16LE param for 
compress sink", client->name);
++              pw_stream_connect(stream->stream,
++                              PW_DIRECTION_OUTPUT,
++                              SPA_ID_INVALID,
++                              flags |
++                              PW_STREAM_FLAG_AUTOCONNECT |
++                              PW_STREAM_FLAG_RT_PROCESS |
++                              PW_STREAM_FLAG_MAP_BUFFERS,
++                              raw_params, 1);
++      } else {
++              pw_stream_connect(stream->stream,
++                              PW_DIRECTION_OUTPUT,
++                              SPA_ID_INVALID,
++                              flags |
++                              PW_STREAM_FLAG_AUTOCONNECT |
++                              PW_STREAM_FLAG_RT_PROCESS |
++                              PW_STREAM_FLAG_MAP_BUFFERS,
++                              params, n_params);
++      }
+
+       stream_update_tag_param(stream);
+
+--
+2.43.0
+
diff --git a/meta-multimedia/recipes-multimedia/pipewire/pipewire_1.6.7.bb 
b/meta-multimedia/recipes-multimedia/pipewire/pipewire_1.6.7.bb
index 50feeaf843..90815ba012 100644
--- a/meta-multimedia/recipes-multimedia/pipewire/pipewire_1.6.7.bb
+++ b/meta-multimedia/recipes-multimedia/pipewire/pipewire_1.6.7.bb
@@ -15,6 +15,7 @@ DEPENDS = "dbus"
 SRCREV = "3b2cb4fb037bf6033b87d3c87ee917b2f686d309"
 BRANCH = "${@oe.utils.trim_version('${PV}', 2)}"
 SRC_URI = 
"git://gitlab.freedesktop.org/pipewire/pipewire.git;branch=${BRANCH};protocol=https;tag=${PV}"
+SRC_URI += "file://0001-pipewire-compress-offload.patch"
 SRC_URI += 
"file://0002-spa-plugins-alsa-acp-compat.h-p-is-already-const-do-.patch"
 
 inherit meson pkgconfig systemd gettext useradd
-- 
2.43.0

-=-=-=-=-=-=-=-=-=-=-=-
Links: You receive all messages sent to this group.
View/Reply Online (#128007): 
https://lists.openembedded.org/g/openembedded-devel/message/128007
Mute This Topic: https://lists.openembedded.org/mt/120097215/21656
Group Owner: [email protected]
Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub 
[[email protected]]
-=-=-=-=-=-=-=-=-=-=-=-

Reply via email to