mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-11-03 09:01:54 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			2024 lines
		
	
	
	
		
			59 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2024 lines
		
	
	
	
		
			59 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* Spa ALSA Compress-Offload sink */
 | 
						|
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
 | 
						|
/* SPDX-FileCopyrightText: Copyright © 2022 Asymptotic Inc. */
 | 
						|
/* SPDX-FileCopyrightText: Copyright @ 2023 Carlos Rafael Giani */
 | 
						|
/* SPDX-License-Identifier: MIT */
 | 
						|
 | 
						|
#include <assert.h>
 | 
						|
#include <stddef.h>
 | 
						|
#include <limits.h>
 | 
						|
#include <linux/version.h>
 | 
						|
 | 
						|
#include <spa/monitor/device.h>
 | 
						|
#include <spa/debug/types.h>
 | 
						|
#include <spa/support/loop.h>
 | 
						|
#include <spa/support/plugin.h>
 | 
						|
#include <spa/support/system.h>
 | 
						|
#include <spa/node/keys.h>
 | 
						|
#include <spa/node/node.h>
 | 
						|
#include <spa/node/utils.h>
 | 
						|
#include <spa/param/audio/format.h>
 | 
						|
#include <spa/param/audio/format-utils.h>
 | 
						|
#include <spa/pod/filter.h>
 | 
						|
#include <spa/pod/parser.h>
 | 
						|
#include <spa/utils/defs.h>
 | 
						|
#include <spa/utils/keys.h>
 | 
						|
#include <spa/utils/names.h>
 | 
						|
#include <spa/utils/result.h>
 | 
						|
#include <spa/utils/string.h>
 | 
						|
 | 
						|
#include "alsa.h"
 | 
						|
#include "compress-offload-api.h"
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * This creates a PipeWire sink node which uses the ALSA Compress-Offload API
 | 
						|
 * for writing compressed data ike MP3, FLAC etc. to an DSP that can handle
 | 
						|
 * such data directly.
 | 
						|
 *
 | 
						|
 * These show up under /dev/snd like "comprCxDx", as opposed to regular
 | 
						|
 * ALSA PCM devices. This sink node still refers to those devices in
 | 
						|
 * regular ALSA fashion as "hw:x,y" devices, where x = card number and
 | 
						|
 * y = device number. For example, "hw:4,7" maps to /dev/snd/comprC4D7.
 | 
						|
 *
 | 
						|
 * ## Example configuration
 | 
						|
 *\code{.unparsed}
 | 
						|
 * context.objects = [
 | 
						|
 *  {   factory = adapter
 | 
						|
 *      args = {
 | 
						|
 *          factory.name   = "api.alsa.compress.offload.sink"
 | 
						|
 *          node.name      = "Compress-Offload-Sink"
 | 
						|
 *          node.description = "Audio sink for compressed audio"
 | 
						|
 *          media.class    = "Audio/Sink"
 | 
						|
 *          api.alsa.path  = "hw:0,3"
 | 
						|
 *          node.param.PortConfig = {
 | 
						|
 *              direction = Input
 | 
						|
 *              mode = passthrough
 | 
						|
 *          }
 | 
						|
 *      }
 | 
						|
 *  }
 | 
						|
 *]
 | 
						|
 *\endcode
 | 
						|
 *
 | 
						|
 * TODO:
 | 
						|
 * - DLL for adjusting driver timer intervals to match the device timestamps in on_driver_timeout()
 | 
						|
 * - Automatic loading using alsa-udev
 | 
						|
 */
 | 
						|
 | 
						|
 | 
						|
/* FLAC support has been present in kernel headers older than 5.5.
 | 
						|
 * However, those older versions don't support FLAC decoding params. */
 | 
						|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0)
 | 
						|
#define COMPRESS_OFFLOAD_HAS_FLAC_DEC_PARAMS
 | 
						|
#endif
 | 
						|
 | 
						|
/* Prior to kernel 5.7, WMA9 Pro/Lossless and WMA10 Lossless
 | 
						|
 * codec profiles were missing.
 | 
						|
 * As for ALAC and Monkey's Audio (APE), those are new in 5.7. */
 | 
						|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0)
 | 
						|
#define COMPRESS_OFFLOAD_SUPPORTS_WMA9_PRO
 | 
						|
#define COMPRESS_OFFLOAD_SUPPORTS_WMA9_LOSSLESS
 | 
						|
#define COMPRESS_OFFLOAD_SUPPORTS_WMA10_LOSSLESS
 | 
						|
#define COMPRESS_OFFLOAD_SUPPORTS_ALAC
 | 
						|
#define COMPRESS_OFFLOAD_SUPPORTS_APE
 | 
						|
#endif
 | 
						|
 | 
						|
 | 
						|
#define CHECK_PORT(this, d, p)              (((d) == SPA_DIRECTION_INPUT) && ((p) == 0))
 | 
						|
 | 
						|
#define MAX_BUFFERS                         (32)
 | 
						|
 | 
						|
#define BUFFER_FLAG_AVAILABLE_FOR_NEW_DATA  (1 << 0)
 | 
						|
 | 
						|
 | 
						|
/* Information about a buffer that got allocated by the PW graph. */
 | 
						|
struct buffer {
 | 
						|
	uint32_t id;
 | 
						|
	uint32_t flags;
 | 
						|
	struct spa_buffer *buf;
 | 
						|
	struct spa_list link;
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
/* Node properties. These are accessible through SPA_PARAM_Props. */
 | 
						|
struct props {
 | 
						|
	/* The'"hw:<cardnr>:<devicenr>" device. */
 | 
						|
	char device[128];
 | 
						|
	/* These are the card and device numbers from the
 | 
						|
	 * from the "hw:<cardnr>:<devicenr>" device.*/
 | 
						|
	int card_nr;
 | 
						|
	int device_nr;
 | 
						|
	bool device_name_set;
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
/* Main sink node structure. */
 | 
						|
struct impl {
 | 
						|
	/* Basic states */
 | 
						|
 | 
						|
	struct spa_handle handle;
 | 
						|
	struct spa_node node;
 | 
						|
 | 
						|
	struct spa_log *log;
 | 
						|
	struct spa_loop *data_loop;
 | 
						|
	struct spa_system *data_system;
 | 
						|
 | 
						|
	struct spa_hook_list hooks;
 | 
						|
	struct spa_callbacks callbacks;
 | 
						|
 | 
						|
	struct props props;
 | 
						|
 | 
						|
	bool have_format;
 | 
						|
	struct spa_audio_info current_audio_info;
 | 
						|
 | 
						|
	/* This is set to true when the SPA_NODE_COMMAND_Start is
 | 
						|
	 * received, and set back to false when SPA_NODE_COMMAND_Pause
 | 
						|
	 * or SPA_NODE_COMMAND_Suspend is received. */
 | 
						|
	bool started;
 | 
						|
 | 
						|
	bool freewheel;
 | 
						|
 | 
						|
	/* SPA buffer states */
 | 
						|
 | 
						|
	struct buffer buffers[MAX_BUFFERS];
 | 
						|
	unsigned int n_buffers;
 | 
						|
	struct spa_list queued_output_buffers;
 | 
						|
	size_t offset_within_oldest_output_buffer;
 | 
						|
 | 
						|
	/* Driver and cycle specific states */
 | 
						|
 | 
						|
	int driver_timerfd;
 | 
						|
	struct spa_source driver_timer_source;
 | 
						|
	uint64_t next_driver_time;
 | 
						|
	bool following;
 | 
						|
	/* Duration and rate of one graph cycle.
 | 
						|
	 * The duration equals the quantum size. */
 | 
						|
	uint32_t cycle_duration;
 | 
						|
	int cycle_rate;
 | 
						|
 | 
						|
	/* Node specific states */
 | 
						|
 | 
						|
	uint64_t node_info_all;
 | 
						|
	struct spa_node_info node_info;
 | 
						|
#define NODE_PropInfo        0
 | 
						|
#define NODE_Props           1
 | 
						|
#define NODE_IO              2
 | 
						|
#define NODE_EnumPortConfig  3
 | 
						|
#define N_NODE_PARAMS        4
 | 
						|
	struct spa_param_info node_params[N_NODE_PARAMS];
 | 
						|
	struct spa_io_clock *node_clock_io;
 | 
						|
	struct spa_io_position *node_position_io;
 | 
						|
 | 
						|
	/* Port specific states */
 | 
						|
 | 
						|
	uint64_t port_info_all;
 | 
						|
	struct spa_port_info port_info;
 | 
						|
#define PORT_EnumFormat  0
 | 
						|
#define PORT_Format      1
 | 
						|
#define PORT_IO          2
 | 
						|
#define PORT_Buffers     3
 | 
						|
#define N_PORT_PARAMS    4
 | 
						|
	struct spa_param_info port_params[N_PORT_PARAMS];
 | 
						|
	struct spa_io_buffers *port_buffers_io;
 | 
						|
 | 
						|
	/* Compress-Offload specific states */
 | 
						|
 | 
						|
	struct compress_offload_api_context *device_context;
 | 
						|
	struct snd_codec audio_codec_info;
 | 
						|
	bool device_started;
 | 
						|
	uint32_t min_fragment_size;
 | 
						|
	uint32_t max_fragment_size;
 | 
						|
	uint32_t min_num_fragments;
 | 
						|
	uint32_t max_num_fragments;
 | 
						|
	uint32_t configured_fragment_size;
 | 
						|
	uint32_t configured_num_fragments;
 | 
						|
	bool device_is_paused;
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/* Compress-Offload device and audio codec functions */
 | 
						|
 | 
						|
static int init_audio_codec_info(struct impl *this, struct spa_audio_info *info, uint32_t *out_rate);
 | 
						|
static int device_open(struct impl *this);
 | 
						|
static void device_close(struct impl *this);
 | 
						|
static int device_start(struct impl *this);
 | 
						|
static int device_pause(struct impl *this);
 | 
						|
static int device_resume(struct impl *this);
 | 
						|
static int device_write(struct impl *this, const void *data, uint32_t size);
 | 
						|
 | 
						|
/* Driver timer functions */
 | 
						|
 | 
						|
static int set_driver_timeout(struct impl *this, uint64_t time);
 | 
						|
static int configure_driver_timer(struct impl *this);
 | 
						|
static int start_driver_timer(struct impl *this);
 | 
						|
static void stop_driver_timer(struct impl *this);
 | 
						|
static void on_driver_timeout(struct spa_source *source);
 | 
						|
static inline void check_position_and_clock_config(struct impl *this);
 | 
						|
static void reevaluate_following_state(struct impl *this);
 | 
						|
static void reevaluate_freewheel_state(struct impl *this);
 | 
						|
 | 
						|
/* Miscellaneous functions */
 | 
						|
 | 
						|
static int parse_device(struct impl *this);
 | 
						|
static void reset_props(struct props *props);
 | 
						|
static void clear_buffers(struct impl *this);
 | 
						|
static inline bool is_following(struct impl *this);
 | 
						|
static int do_start(struct impl *this);
 | 
						|
static void do_stop(struct impl *this);
 | 
						|
static int write_queued_output_buffers(struct impl *this);
 | 
						|
 | 
						|
/* Node and port functions */
 | 
						|
 | 
						|
static void emit_node_info(struct impl *this, bool full);
 | 
						|
static void emit_port_info(struct impl *this, bool full);
 | 
						|
static int impl_node_add_listener(void *object,
 | 
						|
                                  struct spa_hook *listener,
 | 
						|
                                  const struct spa_node_events *events,
 | 
						|
                                  void *data);
 | 
						|
static int impl_node_set_callbacks(void *object,
 | 
						|
                                   const struct spa_node_callbacks *callbacks,
 | 
						|
                                   void *data);
 | 
						|
static int impl_node_sync(void *object, int seq);
 | 
						|
static int impl_node_enum_params(void *object, int seq,
 | 
						|
                                 uint32_t id, uint32_t start, uint32_t num,
 | 
						|
                                 const struct spa_pod *filter);
 | 
						|
static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
 | 
						|
		                       const struct spa_pod *param);
 | 
						|
static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size);
 | 
						|
static int impl_node_send_command(void *object, const struct spa_command *command);
 | 
						|
static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id,
 | 
						|
		                      const struct spa_dict *props);
 | 
						|
static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id);
 | 
						|
static int port_enum_formats(struct impl *this, int seq, uint32_t start, uint32_t num,
 | 
						|
                             const struct spa_pod *filter, struct spa_pod_builder *b);
 | 
						|
static int impl_port_enum_params(void *object, int seq,
 | 
						|
                                 enum spa_direction direction, uint32_t port_id,
 | 
						|
                                 uint32_t id, uint32_t start, uint32_t num,
 | 
						|
                                 const struct spa_pod *filter);
 | 
						|
static int port_set_format(void *object,
 | 
						|
                           enum spa_direction direction, uint32_t port_id,
 | 
						|
                           uint32_t flags,
 | 
						|
                           const struct spa_pod *format);
 | 
						|
static int impl_port_set_param(void *object,
 | 
						|
                               enum spa_direction direction, uint32_t port_id,
 | 
						|
                               uint32_t id, uint32_t flags,
 | 
						|
                               const struct spa_pod *param);
 | 
						|
static int impl_port_use_buffers(void *object,
 | 
						|
                                 enum spa_direction direction, uint32_t port_id,
 | 
						|
                                 uint32_t flags,
 | 
						|
                                 struct spa_buffer **buffers, uint32_t n_buffers);
 | 
						|
static int impl_port_set_io(void *object,
 | 
						|
                            enum spa_direction direction,
 | 
						|
                            uint32_t port_id,
 | 
						|
                            uint32_t id,
 | 
						|
                            void *data, size_t size);
 | 
						|
static int impl_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id);
 | 
						|
static int impl_node_process(void *object);
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/* Compress-Offload device and audio codec functions */
 | 
						|
 | 
						|
struct known_codec_info {
 | 
						|
	uint32_t codec_id;
 | 
						|
	uint32_t media_subtype;
 | 
						|
	const char *name;
 | 
						|
};
 | 
						|
 | 
						|
static struct known_codec_info known_codecs[] = {
 | 
						|
	{ SND_AUDIOCODEC_VORBIS, SPA_MEDIA_SUBTYPE_vorbis, "Ogg Vorbis" },
 | 
						|
	{ SND_AUDIOCODEC_MP3, SPA_MEDIA_SUBTYPE_mp3, "MP3" },
 | 
						|
	{ SND_AUDIOCODEC_AAC, SPA_MEDIA_SUBTYPE_aac, "AAC" },
 | 
						|
	{ SND_AUDIOCODEC_FLAC, SPA_MEDIA_SUBTYPE_flac, "FLAC" },
 | 
						|
	{ SND_AUDIOCODEC_WMA, SPA_MEDIA_SUBTYPE_wma, "WMA" },
 | 
						|
#ifdef COMPRESS_OFFLOAD_SUPPORTS_ALAC
 | 
						|
	{ SND_AUDIOCODEC_ALAC, SPA_MEDIA_SUBTYPE_alac, "ALAC" },
 | 
						|
#endif
 | 
						|
#ifdef COMPRESS_OFFLOAD_SUPPORTS_APE
 | 
						|
	{ SND_AUDIOCODEC_APE, SPA_MEDIA_SUBTYPE_ape, "Monkey's Audio (APE)" },
 | 
						|
#endif
 | 
						|
	{ SND_AUDIOCODEC_REAL, SPA_MEDIA_SUBTYPE_ra, "Real Audio" },
 | 
						|
	{ SND_AUDIOCODEC_AMRWB, SPA_MEDIA_SUBTYPE_amr, "AMR wideband" },
 | 
						|
	{ SND_AUDIOCODEC_AMR, SPA_MEDIA_SUBTYPE_amr, "AMR" },
 | 
						|
};
 | 
						|
 | 
						|
static int init_audio_codec_info(struct impl *this, struct spa_audio_info *info, uint32_t *out_rate)
 | 
						|
{
 | 
						|
	struct snd_codec *codec;
 | 
						|
	uint32_t channels, rate;
 | 
						|
	const struct spa_type_info *media_subtype_info;
 | 
						|
 | 
						|
	media_subtype_info = spa_debug_type_find(spa_type_media_subtype, info->media_subtype);
 | 
						|
	if (media_subtype_info == NULL) {
 | 
						|
		spa_log_error(this->log, "%p: media subtype %d is unknown", this, info->media_subtype);
 | 
						|
		return -ENOTSUP;
 | 
						|
	}
 | 
						|
 | 
						|
	memset(&this->audio_codec_info, 0, sizeof(this->audio_codec_info));
 | 
						|
	codec = &this->audio_codec_info;
 | 
						|
 | 
						|
	switch (info->media_subtype) {
 | 
						|
	case SPA_MEDIA_SUBTYPE_vorbis:
 | 
						|
		codec->id = SND_AUDIOCODEC_VORBIS;
 | 
						|
		rate = info->info.vorbis.rate;
 | 
						|
		channels = info->info.vorbis.channels;
 | 
						|
		spa_log_info(this->log, "%p: initialized codec info to Vorbis; rate: %"
 | 
						|
		             PRIu32 "; channels: %" PRIu32, this, rate, channels);
 | 
						|
		break;
 | 
						|
 | 
						|
	case SPA_MEDIA_SUBTYPE_mp3:
 | 
						|
		codec->id = SND_AUDIOCODEC_MP3;
 | 
						|
		rate = info->info.mp3.rate;
 | 
						|
		channels = info->info.mp3.channels;
 | 
						|
		spa_log_info(this->log, "%p: initialized codec info to MP3; rate: %"
 | 
						|
		             PRIu32 "; channels: %" PRIu32, this, rate, channels);
 | 
						|
		break;
 | 
						|
 | 
						|
	case SPA_MEDIA_SUBTYPE_aac:
 | 
						|
		codec->id = SND_AUDIOCODEC_AAC;
 | 
						|
		rate = info->info.aac.rate;
 | 
						|
		channels = info->info.aac.channels;
 | 
						|
		spa_log_info(this->log, "%p: initialized codec info to AAC; rate: %"
 | 
						|
		             PRIu32 "; channels: %" PRIu32, this, rate, channels);
 | 
						|
		break;
 | 
						|
 | 
						|
	case SPA_MEDIA_SUBTYPE_flac:
 | 
						|
		codec->id = SND_AUDIOCODEC_FLAC;
 | 
						|
#ifdef COMPRESS_OFFLOAD_HAS_FLAC_DEC_PARAMS
 | 
						|
		/* The min/max block sizes are from the FLAC specification:
 | 
						|
		 * https://xiph.org/flac/format.html#blocking
 | 
						|
		 *
 | 
						|
		 * The smallest valid frame possible is 11, which
 | 
						|
		 * is why min_frame_size is set to this quantity.
 | 
						|
		 *
 | 
						|
		 * FFmpeg's flac.h specifies 8192 as the average frame size.
 | 
						|
		 * tinycompress' fcplay uses 4x that amount as the max frame
 | 
						|
		 * size to have enough headroom to be safe.
 | 
						|
		 * We do the same here.
 | 
						|
		 *
 | 
						|
		 * sample_size is set to 0. According to the FLAC spec, this
 | 
						|
		 * is OK to do if a STREAMINFO block was sent into the device
 | 
						|
		 * (see: https://xiph.org/flac/format.html#frame_header), and
 | 
						|
		 * we deal with full FLAC streams here, not just single frames. */
 | 
						|
		codec->options.flac_d.min_blk_size = 16;
 | 
						|
		codec->options.flac_d.max_blk_size = 65535;
 | 
						|
		codec->options.flac_d.min_frame_size = 11;
 | 
						|
		codec->options.flac_d.max_frame_size = 8192 * 4;
 | 
						|
		codec->options.flac_d.sample_size = 0;
 | 
						|
#endif
 | 
						|
		rate = info->info.flac.rate;
 | 
						|
		channels = info->info.flac.channels;
 | 
						|
		spa_log_info(this->log, "%p: initialized codec info to FLAC; rate: %"
 | 
						|
	                     PRIu32 "; channels: %" PRIu32, this, rate, channels);
 | 
						|
		break;
 | 
						|
 | 
						|
	case SPA_MEDIA_SUBTYPE_wma: {
 | 
						|
		const char *profile_name;
 | 
						|
		codec->id = SND_AUDIOCODEC_WMA;
 | 
						|
		/* WMA does not work with Compress-Offload
 | 
						|
		 * if codec profile is not set. */
 | 
						|
		switch (info->info.wma.profile) {
 | 
						|
		case SPA_AUDIO_WMA_PROFILE_WMA9:
 | 
						|
			codec->profile = SND_AUDIOPROFILE_WMA9;
 | 
						|
			profile_name = "WMA9";
 | 
						|
			break;
 | 
						|
		case SPA_AUDIO_WMA_PROFILE_WMA10:
 | 
						|
			codec->profile = SND_AUDIOPROFILE_WMA10;
 | 
						|
			profile_name = "WMA10";
 | 
						|
			break;
 | 
						|
#ifdef COMPRESS_OFFLOAD_SUPPORTS_WMA9_PRO
 | 
						|
		case SPA_AUDIO_WMA_PROFILE_WMA9_PRO:
 | 
						|
			codec->profile = SND_AUDIOPROFILE_WMA9_PRO;
 | 
						|
			profile_name = "WMA9 Pro";
 | 
						|
			break;
 | 
						|
#endif
 | 
						|
#ifdef COMPRESS_OFFLOAD_SUPPORTS_WMA9_LOSSLESS
 | 
						|
		case SPA_AUDIO_WMA_PROFILE_WMA9_LOSSLESS:
 | 
						|
			codec->profile = SND_AUDIOPROFILE_WMA9_LOSSLESS;
 | 
						|
			profile_name = "WMA9 Lossless";
 | 
						|
			break;
 | 
						|
#endif
 | 
						|
#ifdef COMPRESS_OFFLOAD_SUPPORTS_WMA10_LOSSLESS
 | 
						|
		case SPA_AUDIO_WMA_PROFILE_WMA10_LOSSLESS:
 | 
						|
			codec->profile = SND_AUDIOPROFILE_WMA10_LOSSLESS;
 | 
						|
			profile_name = "WMA10 Lossless";
 | 
						|
			break;
 | 
						|
#endif
 | 
						|
		default:
 | 
						|
			spa_log_error(this->log, "%p: Invalid WMA profile", this);
 | 
						|
			return -EINVAL;
 | 
						|
		}
 | 
						|
		codec->bit_rate = info->info.wma.bitrate;
 | 
						|
		codec->align = info->info.wma.block_align;
 | 
						|
		rate = info->info.wma.rate;
 | 
						|
		channels = info->info.wma.channels;
 | 
						|
		spa_log_info(this->log, "%p: initialized codec info to WMA; rate: %"
 | 
						|
		             PRIu32 "; channels: %" PRIu32 "; profile %s", this,
 | 
						|
		             rate, channels, profile_name);
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
#ifdef COMPRESS_OFFLOAD_SUPPORTS_ALAC
 | 
						|
	case SPA_MEDIA_SUBTYPE_alac:
 | 
						|
		codec->id = SND_AUDIOCODEC_ALAC;
 | 
						|
		rate = info->info.alac.rate;
 | 
						|
		channels = info->info.alac.channels;
 | 
						|
		spa_log_info(this->log, "%p: initialized codec info to ALAC; rate: %"
 | 
						|
		             PRIu32 "; channels: %" PRIu32, this, rate, channels);
 | 
						|
		break;
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef COMPRESS_OFFLOAD_SUPPORTS_APE
 | 
						|
	case SPA_MEDIA_SUBTYPE_ape:
 | 
						|
		codec->id = SND_AUDIOCODEC_APE;
 | 
						|
		rate = info->info.ape.rate;
 | 
						|
		channels = info->info.ape.channels;
 | 
						|
		spa_log_info(this->log, "%p: initialized codec info to APE (Monkey's Audio);"
 | 
						|
		             " rate: %" PRIu32 "; channels: %" PRIu32, this, rate, channels);
 | 
						|
		break;
 | 
						|
#endif
 | 
						|
 | 
						|
	case SPA_MEDIA_SUBTYPE_ra:
 | 
						|
		codec->id = SND_AUDIOCODEC_REAL;
 | 
						|
		rate = info->info.ra.rate;
 | 
						|
		channels = info->info.ra.channels;
 | 
						|
		spa_log_info(this->log, "%p: initialized codec info to Real Audio; rate: %"
 | 
						|
		             PRIu32 "; channels: %" PRIu32, this, rate, channels);
 | 
						|
		break;
 | 
						|
 | 
						|
	case SPA_MEDIA_SUBTYPE_amr:
 | 
						|
		if (info->info.amr.band_mode == SPA_AUDIO_AMR_BAND_MODE_WB)
 | 
						|
			codec->id = SND_AUDIOCODEC_AMRWB;
 | 
						|
		else
 | 
						|
			codec->id = SND_AUDIOCODEC_AMR;
 | 
						|
		rate = info->info.amr.rate;
 | 
						|
		channels = info->info.amr.channels;
 | 
						|
		spa_log_info(this->log, "%p: initialized codec info to %s; rate: %"
 | 
						|
		             PRIu32 "; channels: %" PRIu32, this,
 | 
						|
		             (codec->id == SND_AUDIOCODEC_AMRWB) ? "AMR wideband" : "AMR",
 | 
						|
		             rate, channels);
 | 
						|
		break;
 | 
						|
 | 
						|
	default:
 | 
						|
		spa_log_error(this->log, "%p: media subtype %s is not supported", this, media_subtype_info->name);
 | 
						|
		return -ENOTSUP;
 | 
						|
	}
 | 
						|
 | 
						|
	codec->ch_in = channels;
 | 
						|
	codec->ch_out = channels;
 | 
						|
	codec->sample_rate = rate;
 | 
						|
 | 
						|
	codec->rate_control = 0;
 | 
						|
	codec->level = 0;
 | 
						|
	codec->ch_mode = 0;
 | 
						|
	codec->format = 0;
 | 
						|
 | 
						|
	*out_rate = rate;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int device_open(struct impl *this)
 | 
						|
{
 | 
						|
	assert(this->device_context == NULL);
 | 
						|
 | 
						|
	spa_log_info(this->log, "%p: opening Compress-Offload device, card #%d device #%d",
 | 
						|
	              this, this->props.card_nr, this->props.device_nr);
 | 
						|
 | 
						|
	this->device_context = compress_offload_api_open(this->props.card_nr, this->props.device_nr, this->log);
 | 
						|
	if (this->device_context == NULL)
 | 
						|
		return -errno;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void device_close(struct impl *this)
 | 
						|
{
 | 
						|
	if (this->device_context == NULL)
 | 
						|
		return;
 | 
						|
 | 
						|
	spa_log_info(this->log, "%p: closing Compress-Offload device, card #%d device #%d",
 | 
						|
	              this, this->props.card_nr, this->props.device_nr);
 | 
						|
 | 
						|
	if (this->device_started)
 | 
						|
		compress_offload_api_stop(this->device_context);
 | 
						|
 | 
						|
	compress_offload_api_close(this->device_context);
 | 
						|
 | 
						|
	this->device_context = NULL;
 | 
						|
	this->device_started = false;
 | 
						|
	this->device_is_paused = false;
 | 
						|
 | 
						|
	this->have_format = false;
 | 
						|
}
 | 
						|
 | 
						|
static int device_start(struct impl *this)
 | 
						|
{
 | 
						|
	assert(this->device_context != NULL);
 | 
						|
 | 
						|
	if (compress_offload_api_start(this->device_context) < 0)
 | 
						|
		return -errno;
 | 
						|
 | 
						|
	this->device_started = true;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int device_pause(struct impl *this)
 | 
						|
{
 | 
						|
	/* device_pause() can sometimes be called when the device context is already
 | 
						|
	 * gone. In particular, this can happen when the suspend command is received
 | 
						|
	 * after the pause command. */
 | 
						|
	if (this->device_context == NULL)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	if (this->device_is_paused)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	if (compress_offload_api_pause(this->device_context) < 0)
 | 
						|
		return -errno;
 | 
						|
 | 
						|
	this->device_is_paused = true;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int device_resume(struct impl *this)
 | 
						|
{
 | 
						|
	assert(this->device_context != NULL);
 | 
						|
 | 
						|
	if (!this->device_is_paused)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	if (compress_offload_api_resume(this->device_context) < 0)
 | 
						|
		return -errno;
 | 
						|
 | 
						|
	this->device_is_paused = false;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int device_write(struct impl *this, const void *data, uint32_t size)
 | 
						|
{
 | 
						|
	int res;
 | 
						|
	uint32_t num_bytes_to_write;
 | 
						|
	struct snd_compr_avail available_space;
 | 
						|
 | 
						|
	/* In here, try to write out as much data as possible,
 | 
						|
	 * in a non-blocking manner, retaining the unwritten
 | 
						|
	 * data for the next write call. */
 | 
						|
 | 
						|
	if (SPA_UNLIKELY((res = compress_offload_api_get_available_space(
 | 
						|
	                 this->device_context, &available_space)) < 0))
 | 
						|
		return res;
 | 
						|
 | 
						|
	/* We can only write data if there is at least enough space for one
 | 
						|
	 * fragment's worth of data, or if the data we want to write is
 | 
						|
	 * small (smaller than a fragment). The latter can happen when we
 | 
						|
	 * are writing the last few bits of the compressed audio medium.
 | 
						|
	 * When the former happens, we try to write as much data as we
 | 
						|
	 * can, limited by the amount of space available in the device. */
 | 
						|
	if ((available_space.avail < this->min_fragment_size) && (available_space.avail < size))
 | 
						|
		return 0;
 | 
						|
 | 
						|
	num_bytes_to_write = SPA_MIN(size, available_space.avail);
 | 
						|
	res = compress_offload_api_write(this->device_context, data, num_bytes_to_write);
 | 
						|
 | 
						|
	if (SPA_UNLIKELY(res < 0)) {
 | 
						|
		if (res == -EBADFD)
 | 
						|
			spa_log_debug(this->log, "%p: device is paused", this);
 | 
						|
		else
 | 
						|
			spa_log_error(this->log, "%p: write error: %s", this, spa_strerror(res));
 | 
						|
		return res;
 | 
						|
	}
 | 
						|
 | 
						|
	spa_log_trace_fp(this->log, "%p: wrote %d bytes; original request: %" PRIu32 "; adjusted "
 | 
						|
	                 "for available space in device: %" PRIu32, this, res, size, num_bytes_to_write);
 | 
						|
 | 
						|
	if (SPA_UNLIKELY(((uint32_t)res) > num_bytes_to_write)) {
 | 
						|
		spa_log_error(this->log, "%p: wrote more bytes than what was requested; "
 | 
						|
		              "requested: %" PRId32 " wrote: %d", this, num_bytes_to_write, res);
 | 
						|
		return -EIO;
 | 
						|
	}
 | 
						|
 | 
						|
	return res;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/* Driver timer functions */
 | 
						|
 | 
						|
static int set_driver_timeout(struct impl *this, uint64_t time)
 | 
						|
{
 | 
						|
	struct itimerspec ts;
 | 
						|
 | 
						|
	ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC;
 | 
						|
	ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC;
 | 
						|
	ts.it_interval.tv_sec = 0;
 | 
						|
	ts.it_interval.tv_nsec = 0;
 | 
						|
	spa_system_timerfd_settime(this->data_system, this->driver_timerfd,
 | 
						|
	                           SPA_FD_TIMER_ABSTIME, &ts, NULL);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int configure_driver_timer(struct impl *this)
 | 
						|
{
 | 
						|
	struct timespec now;
 | 
						|
	int res;
 | 
						|
 | 
						|
	if ((res = spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now)) < 0) {
 | 
						|
		spa_log_error(this->log, "%p: could not get time from monotonic sysclock: %s",
 | 
						|
		              this, spa_strerror(res));
 | 
						|
	    return res;
 | 
						|
	}
 | 
						|
	this->next_driver_time = SPA_TIMESPEC_TO_NSEC(&now);
 | 
						|
 | 
						|
	if (this->following)
 | 
						|
		set_driver_timeout(this, 0);
 | 
						|
	else
 | 
						|
		set_driver_timeout(this, this->next_driver_time);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int start_driver_timer(struct impl *this)
 | 
						|
{
 | 
						|
	int res;
 | 
						|
 | 
						|
	spa_log_debug(this->log, "%p: starting driver timer", this);
 | 
						|
 | 
						|
	this->driver_timer_source.func = on_driver_timeout;
 | 
						|
	this->driver_timer_source.data = this;
 | 
						|
	this->driver_timer_source.fd = this->driver_timerfd;
 | 
						|
	this->driver_timer_source.mask = SPA_IO_IN;
 | 
						|
	this->driver_timer_source.rmask = 0;
 | 
						|
 | 
						|
	spa_loop_add_source(this->data_loop, &this->driver_timer_source);
 | 
						|
 | 
						|
	if (SPA_UNLIKELY((res = configure_driver_timer(this)) < 0))
 | 
						|
		return res;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int do_remove_driver_timer_source(struct spa_loop *loop,
 | 
						|
                                         bool async,
 | 
						|
                                         uint32_t seq,
 | 
						|
                                         const void *data,
 | 
						|
                                         size_t size,
 | 
						|
                                         void *user_data)
 | 
						|
{
 | 
						|
	struct impl *this = user_data;
 | 
						|
 | 
						|
	spa_loop_remove_source(this->data_loop, &this->driver_timer_source);
 | 
						|
	set_driver_timeout(this, 0);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void stop_driver_timer(struct impl *this)
 | 
						|
{
 | 
						|
	spa_log_debug(this->log, "%p: stopping driver timer", this);
 | 
						|
 | 
						|
	/* Perform the actual stop within
 | 
						|
	 * the dataloop to avoid data races. */
 | 
						|
	spa_loop_invoke(this->data_loop, do_remove_driver_timer_source, 0, NULL, 0, true, this);
 | 
						|
}
 | 
						|
 | 
						|
static void on_driver_timeout(struct spa_source *source)
 | 
						|
{
 | 
						|
	struct impl *this = source->data;
 | 
						|
 | 
						|
	uint64_t expire, current_time;
 | 
						|
	int res;
 | 
						|
 | 
						|
	if (SPA_LIKELY(this->started)) {
 | 
						|
		if (SPA_UNLIKELY((res = spa_system_timerfd_read(this->data_system,
 | 
						|
		                  this->driver_timerfd, &expire)) < 0)) {
 | 
						|
			if (res != -EAGAIN)
 | 
						|
				spa_log_warn(this->log, "%p: error reading from timerfd: %s",
 | 
						|
				             this, spa_strerror(res));
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (SPA_LIKELY(this->node_position_io != NULL)) {
 | 
						|
		this->cycle_duration = this->node_position_io->clock.target_duration;
 | 
						|
		this->cycle_rate = this->node_position_io->clock.target_rate.denom;
 | 
						|
	} else {
 | 
						|
		/* This can happen at the very beginning if node_position_io
 | 
						|
		 * isn't passed to this node in time. */
 | 
						|
		this->cycle_duration = 1024;
 | 
						|
		this->cycle_rate = 48000;
 | 
						|
	}
 | 
						|
 | 
						|
	current_time = this->next_driver_time;
 | 
						|
 | 
						|
	this->next_driver_time += ((uint64_t)(this->cycle_duration)) * 1000000000ULL / this->cycle_rate;
 | 
						|
	if (this->node_clock_io != NULL) {
 | 
						|
		this->node_clock_io->nsec = current_time;
 | 
						|
		this->node_clock_io->rate = this->node_clock_io->target_rate;
 | 
						|
		this->node_clock_io->position += this->node_clock_io->duration;
 | 
						|
		this->node_clock_io->duration = this->cycle_duration;
 | 
						|
		this->node_clock_io->delay = 0;
 | 
						|
		this->node_clock_io->rate_diff = 1.0;
 | 
						|
		this->node_clock_io->next_nsec = this->next_driver_time;
 | 
						|
		spa_log_trace_fp(this->log, "%p: clock IO updated to: nsec %" PRIu64
 | 
						|
			" pos %" PRIu64 " dur %" PRIu64 " next-nsec %" PRIu64, this,
 | 
						|
			this->node_clock_io->nsec, this->node_clock_io->position,
 | 
						|
			this->node_clock_io->duration, this->node_clock_io->next_nsec);
 | 
						|
	}
 | 
						|
 | 
						|
	/* Adapt the graph cycle progression to the needs of the sink.
 | 
						|
	 * If the sink still has data to output, don't advance. */
 | 
						|
	if (spa_list_is_empty(&this->queued_output_buffers)) {
 | 
						|
		struct spa_io_buffers *io = this->port_buffers_io;
 | 
						|
 | 
						|
		if (SPA_LIKELY(io != NULL)) {
 | 
						|
			spa_log_trace_fp(this->log, "%p: ran out of buffers to output, "
 | 
						|
			                 "need more; IO status: %d", this, io->status);
 | 
						|
			io->status = SPA_STATUS_NEED_DATA;
 | 
						|
			spa_node_call_ready(&this->callbacks, SPA_STATUS_NEED_DATA);
 | 
						|
		} else {
 | 
						|
			/* This should not happen. If it does, then there may be
 | 
						|
			 * an error in when the timer is stopped. When it happens,
 | 
						|
			 * do not schedule a next timeout. */
 | 
						|
			spa_log_warn(this->log, "%p: buffers IO was set to NULL before "
 | 
						|
			             "the driver timer was stopped", this);
 | 
						|
			set_driver_timeout(this, 0);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		write_queued_output_buffers(this);
 | 
						|
	}
 | 
						|
 | 
						|
	// TODO check for impossible timeouts (only relevant when taking device timestamps into account)
 | 
						|
 | 
						|
	set_driver_timeout(this, this->next_driver_time);
 | 
						|
}
 | 
						|
 | 
						|
static inline void check_position_and_clock_config(struct impl *this)
 | 
						|
{
 | 
						|
	if (SPA_LIKELY(this->node_position_io != NULL)) {
 | 
						|
		this->cycle_duration = this->node_position_io->clock.duration;
 | 
						|
		this->cycle_rate = this->node_position_io->clock.rate.denom;
 | 
						|
	} else {
 | 
						|
		/* This can happen at the very beginning if node_position_io
 | 
						|
		 * isn't passed to this node in time. */
 | 
						|
		this->cycle_duration = 1024;
 | 
						|
		this->cycle_rate = 48000;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static int do_reevaluate_following_state(struct spa_loop *loop,
 | 
						|
			    bool async,
 | 
						|
			    uint32_t seq,
 | 
						|
			    const void *data,
 | 
						|
			    size_t size,
 | 
						|
			    void *user_data)
 | 
						|
{
 | 
						|
	struct impl *this = user_data;
 | 
						|
	configure_driver_timer(this);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void reevaluate_following_state(struct impl *this)
 | 
						|
{
 | 
						|
	bool following;
 | 
						|
 | 
						|
	if (!this->started)
 | 
						|
		return;
 | 
						|
 | 
						|
	following = is_following(this);
 | 
						|
	if (following != this->following) {
 | 
						|
		spa_log_debug(this->log, "%p: following state changed: %d->%d", this, this->following, following);
 | 
						|
		this->following = following;
 | 
						|
		spa_loop_invoke(this->data_loop, do_reevaluate_following_state, 0, NULL, 0, true, this);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void reevaluate_freewheel_state(struct impl *this)
 | 
						|
{
 | 
						|
	bool freewheel;
 | 
						|
 | 
						|
	if (!this->started)
 | 
						|
		return;
 | 
						|
 | 
						|
	freewheel = (this->node_position_io != NULL) &&
 | 
						|
		SPA_FLAG_IS_SET(this->node_position_io->clock.flags, SPA_IO_CLOCK_FLAG_FREEWHEEL);
 | 
						|
 | 
						|
	if (this->freewheel != freewheel) {
 | 
						|
		spa_log_debug(this->log, "%p: freewheel state changed: %d->%d", this, this->freewheel, freewheel);
 | 
						|
		this->freewheel = freewheel;
 | 
						|
		if (freewheel)
 | 
						|
			device_pause(this);
 | 
						|
		else
 | 
						|
			device_resume(this);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/* Miscellaneous functions */
 | 
						|
 | 
						|
static int parse_device(struct impl *this)
 | 
						|
{
 | 
						|
	char *device;
 | 
						|
	char *nextptr;
 | 
						|
#define NUM_DEVICE_VALUES (2)
 | 
						|
	long values[NUM_DEVICE_VALUES];
 | 
						|
	int value_index;
 | 
						|
 | 
						|
	device = this->props.device;
 | 
						|
 | 
						|
	/* Valid devices always match the "hw:<cardnr>,<devicenr>" pattern. */
 | 
						|
 | 
						|
	if (strncmp(device, "hw:", 3) != 0) {
 | 
						|
		spa_log_error(this->log, "%p: device \"%s\" does not begin with \"hw:\"", this, device);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	nextptr = device + 3;
 | 
						|
	for (value_index = 0; ; ++value_index) {
 | 
						|
		const char *value_label;
 | 
						|
 | 
						|
		switch (value_index) {
 | 
						|
		case 0: value_label = "card"; break;
 | 
						|
		case 1: value_label = "device"; break;
 | 
						|
		default: spa_assert_not_reached();
 | 
						|
		}
 | 
						|
 | 
						|
		errno = 0;
 | 
						|
		values[value_index] = strtol(nextptr, &nextptr, 10);
 | 
						|
		if (errno != 0) {
 | 
						|
			spa_log_error(this->log, "%p: device \"%s\" has invalid %s value",
 | 
						|
			              this, device, value_label);
 | 
						|
			return -EINVAL;
 | 
						|
		}
 | 
						|
 | 
						|
		if (values[value_index] < 0) {
 | 
						|
			spa_log_error(this->log, "%p: device \"%s\" has negative %s value",
 | 
						|
			              this, device, value_label);
 | 
						|
			return -EINVAL;
 | 
						|
		}
 | 
						|
 | 
						|
		if (values[value_index] > INT_MAX) {
 | 
						|
			spa_log_error(this->log, "%p: device \"%s\" has %s value larger than %d",
 | 
						|
			              this, device, value_label, INT_MAX);
 | 
						|
			return -EINVAL;
 | 
						|
		}
 | 
						|
 | 
						|
		if (value_index >= (NUM_DEVICE_VALUES - 1))
 | 
						|
			break;
 | 
						|
 | 
						|
		if ((*nextptr) != ',') {
 | 
						|
			spa_log_error(this->log, "%p: expected ',' separator between numbers in "
 | 
						|
			              "device \"%s\", got '%c'", this, device, *nextptr);
 | 
						|
			return -EINVAL;
 | 
						|
		}
 | 
						|
 | 
						|
		/* Skip the comma between the values. */
 | 
						|
		nextptr++;
 | 
						|
	}
 | 
						|
 | 
						|
	this->props.card_nr = values[0];
 | 
						|
	this->props.device_nr = values[1];
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void reset_props(struct props *props)
 | 
						|
{
 | 
						|
	memset(props->device, 0, sizeof(props->device));
 | 
						|
	props->card_nr = 0;
 | 
						|
	props->device_nr = 0;
 | 
						|
	props->device_name_set = false;
 | 
						|
}
 | 
						|
 | 
						|
static void clear_buffers(struct impl *this)
 | 
						|
{
 | 
						|
	if (this->n_buffers > 0) {
 | 
						|
		spa_log_debug(this->log, "%p: clearing buffers", this);
 | 
						|
		spa_list_init(&this->queued_output_buffers);
 | 
						|
		this->n_buffers = 0;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static inline bool is_following(struct impl *this)
 | 
						|
{
 | 
						|
	return (this->node_position_io != NULL) &&
 | 
						|
	       (this->node_clock_io != NULL) &&
 | 
						|
	       (this->node_position_io->clock.id != this->node_clock_io->id);
 | 
						|
}
 | 
						|
 | 
						|
static int do_start(struct impl *this)
 | 
						|
{
 | 
						|
	int res;
 | 
						|
 | 
						|
	if (this->started)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	this->following = is_following(this);
 | 
						|
	spa_log_debug(this->log, "%p: starting output; starting as follower: %d",
 | 
						|
	              this, this->following);
 | 
						|
 | 
						|
	if (SPA_UNLIKELY((res = start_driver_timer(this)) < 0))
 | 
						|
		return res;
 | 
						|
 | 
						|
	this->started = true;
 | 
						|
 | 
						|
	/* Not starting the compress-offload device here right away.
 | 
						|
	 * That's because we first need to give it at least one
 | 
						|
	 * fragment's worth of data. Starting the device prior to
 | 
						|
	 * that results in buffer underflows inside the device. */
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void do_stop(struct impl *this)
 | 
						|
{
 | 
						|
	if (!this->started)
 | 
						|
		return;
 | 
						|
 | 
						|
	spa_log_debug(this->log, "%p: stopping output", this);
 | 
						|
 | 
						|
	device_pause(this);
 | 
						|
 | 
						|
	this->started = false;
 | 
						|
 | 
						|
	stop_driver_timer(this);
 | 
						|
}
 | 
						|
 | 
						|
static int write_queued_output_buffers(struct impl *this)
 | 
						|
{
 | 
						|
	int res;
 | 
						|
	uint32_t total_num_written_bytes;
 | 
						|
	bool wrote_data = false;
 | 
						|
 | 
						|
	check_position_and_clock_config(this);
 | 
						|
 | 
						|
	/* In here, we write as much data as possible. The device may
 | 
						|
	 * initially not have sufficient space, but it is possible
 | 
						|
	 * that due to ongoing data consumption, it can accommodate
 | 
						|
	 * for more data in a next attempt, hence the "again" label.
 | 
						|
	 *
 | 
						|
	 * If during the write attempts, only a portion of a chunk
 | 
						|
	 * is written, we must keep track of the portion that hasn't
 | 
						|
	 * been consumed yet. offset_within_oldest_output_buffer
 | 
						|
	 * exists for this purpose. In this sink node, each SPA
 | 
						|
	 * buffer has exactly one chunk, so when a chunk is fully
 | 
						|
	 * consumed, the corresponding buffer is removed from the
 | 
						|
	 * queued_output_buffers list, marked as available, and
 | 
						|
	 * returned to the pool through spa_node_call_reuse_buffer(). */
 | 
						|
again:
 | 
						|
	total_num_written_bytes = 0;
 | 
						|
 | 
						|
	while (!spa_list_is_empty(&this->queued_output_buffers)) {
 | 
						|
		struct buffer *b;
 | 
						|
		struct spa_data *d;
 | 
						|
		uint32_t chunk_start_offset, chunk_size, pending_data_size;
 | 
						|
		bool reuse_buffer = false;
 | 
						|
 | 
						|
		b = spa_list_first(&this->queued_output_buffers, struct buffer, link);
 | 
						|
		d = b->buf->datas;
 | 
						|
		assert(b->buf->n_datas >= 1);
 | 
						|
 | 
						|
		chunk_start_offset = d[0].chunk->offset + this->offset_within_oldest_output_buffer;
 | 
						|
		chunk_size = d[0].chunk->size;
 | 
						|
 | 
						|
		/* An empty chunk signals that the source is skipping this cycle. This
 | 
						|
		 * is normal and necessary in cases when the compressed data frames are
 | 
						|
		 * longer than the quantum size. The source then has to keep track of
 | 
						|
		 * the excess lengths, and if these sum up to the length of a quantum,
 | 
						|
		 * it sends a buffer with an empty chunk to compensate. If this is not
 | 
						|
		 * done, there will eventually be an overflow, this sink will miss
 | 
						|
		 * cycles, and audible errors will occur. */
 | 
						|
		if (chunk_size != 0) {
 | 
						|
			int num_written_bytes;
 | 
						|
 | 
						|
			pending_data_size = chunk_size - this->offset_within_oldest_output_buffer;
 | 
						|
 | 
						|
			chunk_start_offset = SPA_MIN(chunk_start_offset, d[0].maxsize);
 | 
						|
			pending_data_size = SPA_MIN(pending_data_size, d[0].maxsize - chunk_start_offset);
 | 
						|
 | 
						|
			num_written_bytes = device_write(this, SPA_PTROFF(d[0].data, chunk_start_offset, void), pending_data_size);
 | 
						|
			if (SPA_UNLIKELY(num_written_bytes < 0))
 | 
						|
				return num_written_bytes;
 | 
						|
			if (num_written_bytes == 0)
 | 
						|
				break;
 | 
						|
 | 
						|
			this->offset_within_oldest_output_buffer += num_written_bytes;
 | 
						|
 | 
						|
			total_num_written_bytes += num_written_bytes;
 | 
						|
			wrote_data = wrote_data || (num_written_bytes > 0);
 | 
						|
 | 
						|
			if (this->offset_within_oldest_output_buffer >= chunk_size) {
 | 
						|
				spa_log_trace_fp(this->log, "%p: buffer with ID %u was fully written; reusing this buffer", this, b->id);
 | 
						|
				reuse_buffer = true;
 | 
						|
				this->offset_within_oldest_output_buffer = 0;
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			spa_log_trace_fp(this->log, "%p: buffer with ID %u has empty chunk; reusing this buffer", this, b->id);
 | 
						|
			reuse_buffer = true;
 | 
						|
		}
 | 
						|
 | 
						|
		if (reuse_buffer) {
 | 
						|
			spa_list_remove(&b->link);
 | 
						|
			SPA_FLAG_SET(b->flags, BUFFER_FLAG_AVAILABLE_FOR_NEW_DATA);
 | 
						|
			this->port_buffers_io->buffer_id = b->id;
 | 
						|
			spa_node_call_reuse_buffer(&this->callbacks, 0, b->id);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (!spa_list_is_empty(&this->queued_output_buffers) && (total_num_written_bytes > 0))
 | 
						|
		goto again;
 | 
						|
 | 
						|
	/* We start the device only after having written data to avoid
 | 
						|
	 * underruns due to an under-populated device ringbuffer. */
 | 
						|
	if (wrote_data && !this->device_started) {
 | 
						|
		spa_log_debug(this->log, "%p: starting device", this);
 | 
						|
		if ((res = device_start(this)) < 0) {
 | 
						|
			spa_log_error(this->log, "%p: starting device failed: %s", this, spa_strerror(res));
 | 
						|
			return res;
 | 
						|
		}
 | 
						|
		this->device_started = true;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* Node and port functions */
 | 
						|
 | 
						|
static const struct spa_dict_item node_info_items[] = {
 | 
						|
	{ SPA_KEY_DEVICE_API, "alsa" },
 | 
						|
	{ SPA_KEY_MEDIA_CLASS, "Audio/Sink" },
 | 
						|
	{ SPA_KEY_NODE_DRIVER, "true" },
 | 
						|
	{ SPA_KEY_NODE_PAUSE_ON_IDLE, "true" },
 | 
						|
};
 | 
						|
 | 
						|
static void emit_node_info(struct impl *this, bool full)
 | 
						|
{
 | 
						|
	uint64_t old = full ? this->node_info.change_mask : 0;
 | 
						|
 | 
						|
	if (full)
 | 
						|
		this->node_info.change_mask = this->node_info_all;
 | 
						|
	if (this->node_info.change_mask) {
 | 
						|
		this->node_info.props = &SPA_DICT_INIT_ARRAY(node_info_items);
 | 
						|
		spa_node_emit_info(&this->hooks, &this->node_info);
 | 
						|
		this->node_info.change_mask = old;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void emit_port_info(struct impl *this, bool full)
 | 
						|
{
 | 
						|
	uint64_t old = full ? this->port_info.change_mask : 0;
 | 
						|
 | 
						|
	if (full)
 | 
						|
		this->port_info.change_mask = this->port_info_all;
 | 
						|
	if (this->port_info.change_mask) {
 | 
						|
		spa_node_emit_port_info(&this->hooks,
 | 
						|
		                        SPA_DIRECTION_INPUT, 0, &this->port_info);
 | 
						|
		this->port_info.change_mask = old;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static int impl_node_add_listener(void *object,
 | 
						|
                                  struct spa_hook *listener,
 | 
						|
                                  const struct spa_node_events *events,
 | 
						|
                                  void *data)
 | 
						|
{
 | 
						|
	struct impl *this = object;
 | 
						|
	struct spa_hook_list save;
 | 
						|
 | 
						|
	spa_return_val_if_fail(this != NULL, -EINVAL);
 | 
						|
 | 
						|
	spa_hook_list_isolate(&this->hooks, &save, listener, events, data);
 | 
						|
 | 
						|
	emit_node_info(this, true);
 | 
						|
	emit_port_info(this, true);
 | 
						|
 | 
						|
	spa_hook_list_join(&this->hooks, &save);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int impl_node_set_callbacks(void *object,
 | 
						|
                                   const struct spa_node_callbacks *callbacks,
 | 
						|
                                   void *data)
 | 
						|
{
 | 
						|
	struct impl *this = object;
 | 
						|
 | 
						|
	spa_return_val_if_fail(this != NULL, -EINVAL);
 | 
						|
 | 
						|
	this->callbacks = SPA_CALLBACKS_INIT(callbacks, data);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int impl_node_sync(void *object, int seq)
 | 
						|
{
 | 
						|
	struct impl *this = object;
 | 
						|
 | 
						|
	spa_return_val_if_fail(this != NULL, -EINVAL);
 | 
						|
 | 
						|
	spa_node_emit_result(&this->hooks, seq, 0, 0, NULL);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int impl_node_enum_params(void *object, int seq,
 | 
						|
                                 uint32_t id, uint32_t start, uint32_t num,
 | 
						|
                                 const struct spa_pod *filter)
 | 
						|
{
 | 
						|
	struct impl *this = object;
 | 
						|
	struct spa_pod *param;
 | 
						|
	struct spa_pod_builder b = { 0 };
 | 
						|
	uint8_t buffer[4096];
 | 
						|
	struct spa_result_node_params result;
 | 
						|
	uint32_t count = 0;
 | 
						|
 | 
						|
	spa_return_val_if_fail(this != NULL, -EINVAL);
 | 
						|
	spa_return_val_if_fail(num != 0, -EINVAL);
 | 
						|
 | 
						|
	result.id = id;
 | 
						|
	result.next = start;
 | 
						|
next:
 | 
						|
	result.index = result.next++;
 | 
						|
 | 
						|
	spa_pod_builder_init(&b, buffer, sizeof(buffer));
 | 
						|
 | 
						|
	switch (id) {
 | 
						|
	case SPA_PARAM_PropInfo:
 | 
						|
	{
 | 
						|
		struct props *p = &this->props;
 | 
						|
 | 
						|
		switch (result.index) {
 | 
						|
		case 0:
 | 
						|
			param = spa_pod_builder_add_object(&b,
 | 
						|
				SPA_TYPE_OBJECT_PropInfo, id,
 | 
						|
				SPA_PROP_INFO_id,           SPA_POD_Id(SPA_PROP_device),
 | 
						|
				SPA_PROP_INFO_name,         SPA_POD_String(SPA_KEY_API_ALSA_PATH),
 | 
						|
				SPA_PROP_INFO_description,  SPA_POD_String("The ALSA Compress-Offload device"),
 | 
						|
				SPA_PROP_INFO_type,         SPA_POD_Stringn(p->device, sizeof(p->device)));
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	case SPA_PARAM_Props:
 | 
						|
	{
 | 
						|
		struct props *p = &this->props;
 | 
						|
 | 
						|
		switch (result.index) {
 | 
						|
		case 0:
 | 
						|
			param = spa_pod_builder_add_object(&b,
 | 
						|
				SPA_TYPE_OBJECT_Props, id,
 | 
						|
				SPA_PROP_device,       SPA_POD_Stringn(p->device, sizeof(p->device))
 | 
						|
			);
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	case SPA_PARAM_IO:
 | 
						|
		switch (result.index) {
 | 
						|
		case 0:
 | 
						|
			param = spa_pod_builder_add_object(&b,
 | 
						|
				SPA_TYPE_OBJECT_ParamIO, id,
 | 
						|
				SPA_PARAM_IO_id,   SPA_POD_Id(SPA_IO_Clock),
 | 
						|
				SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock)));
 | 
						|
			break;
 | 
						|
		case 1:
 | 
						|
			param = spa_pod_builder_add_object(&b,
 | 
						|
				SPA_TYPE_OBJECT_ParamIO, id,
 | 
						|
				SPA_PARAM_IO_id,   SPA_POD_Id(SPA_IO_Position),
 | 
						|
				SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_position)));
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
 | 
						|
	case SPA_PARAM_EnumPortConfig:
 | 
						|
	{
 | 
						|
		switch (result.index) {
 | 
						|
		case 0:
 | 
						|
			/* Force ports to be configured to run in passthrough mode.
 | 
						|
			 * This is essential when dealing with compressed data. */
 | 
						|
			param = spa_pod_builder_add_object(&b,
 | 
						|
				SPA_TYPE_OBJECT_ParamPortConfig, id,
 | 
						|
				SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT),
 | 
						|
				SPA_PARAM_PORT_CONFIG_mode,      SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_passthrough)
 | 
						|
			);
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	default:
 | 
						|
		return -ENOENT;
 | 
						|
	}
 | 
						|
 | 
						|
	if (spa_pod_filter(&b, &result.param, param, filter) < 0)
 | 
						|
		goto next;
 | 
						|
 | 
						|
	spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
 | 
						|
 | 
						|
	if (++count != num)
 | 
						|
		goto next;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
 | 
						|
		                       const struct spa_pod *param)
 | 
						|
{
 | 
						|
	struct impl *this = object;
 | 
						|
	int res;
 | 
						|
 | 
						|
	spa_return_val_if_fail(this != NULL, -EINVAL);
 | 
						|
 | 
						|
	switch (id) {
 | 
						|
	case SPA_PARAM_Props:
 | 
						|
	{
 | 
						|
		struct props *p = &this->props;
 | 
						|
 | 
						|
		if (param == NULL) {
 | 
						|
			reset_props(p);
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
 | 
						|
		spa_pod_parse_object(param,
 | 
						|
			SPA_TYPE_OBJECT_Props, NULL,
 | 
						|
			SPA_PROP_device,       SPA_POD_OPT_Stringn(p->device, sizeof(p->device))
 | 
						|
		);
 | 
						|
 | 
						|
		spa_log_debug(this->log, "%p: setting device name to \"%s\"", this, p->device);
 | 
						|
 | 
						|
		p->device_name_set = true;
 | 
						|
 | 
						|
		if ((res = parse_device(this)) < 0) {
 | 
						|
			p->device_name_set = false;
 | 
						|
			return res;
 | 
						|
		}
 | 
						|
 | 
						|
		emit_node_info(this, false);
 | 
						|
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	default:
 | 
						|
		res = -ENOENT;
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	return res;
 | 
						|
}
 | 
						|
 | 
						|
static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
 | 
						|
{
 | 
						|
	struct impl *this = object;
 | 
						|
 | 
						|
	spa_return_val_if_fail(this != NULL, -EINVAL);
 | 
						|
 | 
						|
	switch (id) {
 | 
						|
	case SPA_IO_Clock:
 | 
						|
		spa_log_debug(this->log, "%p: got clock IO", this);
 | 
						|
		this->node_clock_io = data;
 | 
						|
		break;
 | 
						|
	case SPA_IO_Position:
 | 
						|
		spa_log_debug(this->log, "%p: got position IO", this);
 | 
						|
		this->node_position_io = data;
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		return -ENOENT;
 | 
						|
	}
 | 
						|
 | 
						|
	reevaluate_following_state(this);
 | 
						|
	reevaluate_freewheel_state(this);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int impl_node_send_command(void *object, const struct spa_command *command)
 | 
						|
{
 | 
						|
	struct impl *this = object;
 | 
						|
	int res;
 | 
						|
 | 
						|
	spa_return_val_if_fail(this != NULL, -EINVAL);
 | 
						|
	spa_return_val_if_fail(command != NULL, -EINVAL);
 | 
						|
 | 
						|
	spa_log_debug(this->log, "%p: got new command: %s", this,
 | 
						|
		spa_debug_type_find_name(spa_type_node_command_id, SPA_NODE_COMMAND_ID(command)));
 | 
						|
 | 
						|
	switch (SPA_NODE_COMMAND_ID(command)) {
 | 
						|
	case SPA_NODE_COMMAND_ParamBegin:
 | 
						|
		if (SPA_UNLIKELY((res = device_open(this)) < 0))
 | 
						|
			return res;
 | 
						|
		break;
 | 
						|
 | 
						|
	case SPA_NODE_COMMAND_ParamEnd:
 | 
						|
		device_close(this);
 | 
						|
		break;
 | 
						|
 | 
						|
	case SPA_NODE_COMMAND_Start:
 | 
						|
		if (!this->have_format)
 | 
						|
			return -EIO;
 | 
						|
		if (this->n_buffers == 0)
 | 
						|
			return -EIO;
 | 
						|
 | 
						|
		if (SPA_UNLIKELY((res = do_start(this)) < 0))
 | 
						|
			return res;
 | 
						|
 | 
						|
		break;
 | 
						|
 | 
						|
	case SPA_NODE_COMMAND_Suspend:
 | 
						|
	case SPA_NODE_COMMAND_Pause:
 | 
						|
		do_stop(this);
 | 
						|
		break;
 | 
						|
 | 
						|
	default:
 | 
						|
		return -ENOTSUP;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id,
 | 
						|
		                      const struct spa_dict *props)
 | 
						|
{
 | 
						|
	return -ENOTSUP;
 | 
						|
}
 | 
						|
 | 
						|
static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id)
 | 
						|
{
 | 
						|
	return -ENOTSUP;
 | 
						|
}
 | 
						|
 | 
						|
static int port_enum_formats(struct impl *this, int seq, uint32_t start, uint32_t num,
 | 
						|
                             const struct spa_pod *filter, struct spa_pod_builder *b)
 | 
						|
{
 | 
						|
	bool device_started, device_opened;
 | 
						|
	struct spa_result_node_params result;
 | 
						|
	struct spa_pod *fmt;
 | 
						|
	const struct known_codec_info *codec_info;
 | 
						|
	uint32_t count = 0;
 | 
						|
	int res;
 | 
						|
	bool codec_supported;
 | 
						|
	struct spa_audio_info info;
 | 
						|
 | 
						|
	device_opened = (this->device_context != NULL);
 | 
						|
	device_started = this->device_started;
 | 
						|
 | 
						|
	spa_log_debug(this->log, "%p: about to enumerate supported codecs: "
 | 
						|
	              "device opened: %d have configured format: %d device started: %d",
 | 
						|
	              this, device_opened, this->have_format, device_started);
 | 
						|
 | 
						|
	if (!this->started && this->have_format) {
 | 
						|
		spa_log_debug(this->log, "%p: closing device to reset configured format", this);
 | 
						|
		device_close(this);
 | 
						|
		device_opened = false;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!device_opened) {
 | 
						|
		if ((res = device_open(this)) < 0)
 | 
						|
			return res;
 | 
						|
	}
 | 
						|
 | 
						|
	spa_zero(result);
 | 
						|
	result.id = SPA_PARAM_EnumFormat;
 | 
						|
	result.next = start;
 | 
						|
 | 
						|
next:
 | 
						|
	result.index = result.next++;
 | 
						|
 | 
						|
	if (result.index >= SPA_N_ELEMENTS(known_codecs))
 | 
						|
		goto enum_end;
 | 
						|
 | 
						|
	codec_info = &(known_codecs[result.index]);
 | 
						|
 | 
						|
	codec_supported = compress_offload_api_supports_codec(this->device_context, codec_info->codec_id);
 | 
						|
 | 
						|
	spa_log_debug(this->log, "%p: codec %s supported: %s", this,
 | 
						|
	              codec_info->name, codec_supported ? "yes" : "no");
 | 
						|
 | 
						|
	if (!codec_supported)
 | 
						|
		goto next;
 | 
						|
 | 
						|
	spa_zero(info);
 | 
						|
	info.media_type = SPA_MEDIA_TYPE_audio;
 | 
						|
	info.media_subtype = codec_info->media_subtype;
 | 
						|
 | 
						|
	if ((fmt = spa_format_audio_build(b, SPA_PARAM_EnumFormat, &info)) == NULL) {
 | 
						|
		res = -errno;
 | 
						|
		spa_log_error(this->log, "%p: error while building enumerated audio info: %s",
 | 
						|
		              this, spa_strerror(res));
 | 
						|
		return res;
 | 
						|
	}
 | 
						|
 | 
						|
	if (spa_pod_filter(b, &result.param, fmt, filter) < 0)
 | 
						|
		goto next;
 | 
						|
 | 
						|
	spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
 | 
						|
 | 
						|
	if (++count != num)
 | 
						|
		goto next;
 | 
						|
 | 
						|
enum_end:
 | 
						|
	res = 0;
 | 
						|
 | 
						|
	if (!device_opened)
 | 
						|
		device_close(this);
 | 
						|
 | 
						|
	spa_log_debug(this->log, "%p: done enumerating supported codecs", this);
 | 
						|
 | 
						|
	return res;
 | 
						|
}
 | 
						|
 | 
						|
static int impl_port_enum_params(void *object, int seq,
 | 
						|
                                 enum spa_direction direction, uint32_t port_id,
 | 
						|
                                 uint32_t id, uint32_t start, uint32_t num,
 | 
						|
                                 const struct spa_pod *filter)
 | 
						|
{
 | 
						|
	struct impl *this = object;
 | 
						|
	struct spa_pod *param = NULL;
 | 
						|
	struct spa_pod_builder b = { 0 };
 | 
						|
	uint8_t buffer[4096];
 | 
						|
	struct spa_result_node_params result;
 | 
						|
	uint32_t count = 0;
 | 
						|
 | 
						|
	spa_return_val_if_fail(this != NULL, -EINVAL);
 | 
						|
	spa_return_val_if_fail(num != 0, -EINVAL);
 | 
						|
 | 
						|
	spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
 | 
						|
 | 
						|
	result.id = id;
 | 
						|
	result.next = start;
 | 
						|
next:
 | 
						|
	result.index = result.next++;
 | 
						|
 | 
						|
	spa_pod_builder_init(&b, buffer, sizeof(buffer));
 | 
						|
 | 
						|
	switch (id) {
 | 
						|
	case SPA_PARAM_EnumFormat:
 | 
						|
		return port_enum_formats(this, seq, start, num, filter, &b);
 | 
						|
 | 
						|
	case SPA_PARAM_Format:
 | 
						|
		if (!this->have_format) {
 | 
						|
			spa_log_debug(this->log, "%p: attempted to enumerate current "
 | 
						|
			              "format, but no current audio info set", this);
 | 
						|
			return -EIO;
 | 
						|
		}
 | 
						|
 | 
						|
		if (result.index > 0)
 | 
						|
			return 0;
 | 
						|
 | 
						|
		spa_log_debug(this->log, "%p: current audio info is set; "
 | 
						|
		              "enumerating currently set format", this);
 | 
						|
 | 
						|
		param = spa_format_audio_build(&b, id, &this->current_audio_info);
 | 
						|
		break;
 | 
						|
 | 
						|
	case SPA_PARAM_IO:
 | 
						|
		switch (result.index) {
 | 
						|
		case 0:
 | 
						|
			param = spa_pod_builder_add_object(&b,
 | 
						|
			        SPA_TYPE_OBJECT_ParamIO, id,
 | 
						|
			        SPA_PARAM_IO_id,   SPA_POD_Id(SPA_IO_Buffers),
 | 
						|
			        SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers)));
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
 | 
						|
	case SPA_PARAM_Buffers:
 | 
						|
		if (!this->have_format)
 | 
						|
			return -EIO;
 | 
						|
		if (result.index > 0)
 | 
						|
			return 0;
 | 
						|
 | 
						|
		param = spa_pod_builder_add_object(&b,
 | 
						|
			SPA_TYPE_OBJECT_ParamBuffers, id,
 | 
						|
			SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS),
 | 
						|
			/* blocks is set to 1 since we don't have planar data */
 | 
						|
			SPA_PARAM_BUFFERS_blocks,  SPA_POD_Int(1),
 | 
						|
			SPA_PARAM_BUFFERS_size,    SPA_POD_CHOICE_RANGE_Int(
 | 
						|
				this->configured_fragment_size * this->configured_num_fragments,
 | 
						|
				this->configured_fragment_size * this->configured_num_fragments,
 | 
						|
				this->max_fragment_size * this->max_num_fragments),
 | 
						|
			/* "stride" has no meaning when dealing with compressed data */
 | 
						|
			SPA_PARAM_BUFFERS_stride,  SPA_POD_Int(0));
 | 
						|
		break;
 | 
						|
 | 
						|
	default:
 | 
						|
		return -ENOENT;
 | 
						|
	}
 | 
						|
 | 
						|
	if (spa_pod_filter(&b, &result.param, param, filter) < 0)
 | 
						|
		goto next;
 | 
						|
 | 
						|
	spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
 | 
						|
 | 
						|
	if (++count != num)
 | 
						|
		goto next;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int port_set_format(void *object,
 | 
						|
                           enum spa_direction direction, uint32_t port_id,
 | 
						|
                           uint32_t flags,
 | 
						|
                           const struct spa_pod *format)
 | 
						|
{
 | 
						|
	struct impl *this = object;
 | 
						|
	int res;
 | 
						|
 | 
						|
	if (format == NULL) {
 | 
						|
		if (!this->have_format)
 | 
						|
			return 0;
 | 
						|
 | 
						|
		spa_log_debug(this->log, "%p: clearing format and closing device", this);
 | 
						|
		device_close(this);
 | 
						|
		clear_buffers(this);
 | 
						|
	} else {
 | 
						|
		struct spa_audio_info info = { 0 };
 | 
						|
		uint32_t rate;
 | 
						|
		const struct snd_compr_caps *compress_offload_caps;
 | 
						|
 | 
						|
		spa_log_debug(this->log, "%p: about to set format", this);
 | 
						|
 | 
						|
		if ((res = spa_format_audio_parse(format, &info)) < 0) {
 | 
						|
			spa_log_error(this->log, "%p: error while parsing audio format: %s",
 | 
						|
			              this, spa_strerror(res));
 | 
						|
			return res;
 | 
						|
		}
 | 
						|
 | 
						|
		if (this->device_context != NULL) {
 | 
						|
			spa_log_debug(this->log, "%p: need to close device to be able to reopen it with new format", this);
 | 
						|
			device_close(this);
 | 
						|
		}
 | 
						|
 | 
						|
		if ((res = init_audio_codec_info(this, &info, &rate)) < 0)
 | 
						|
			return res;
 | 
						|
 | 
						|
		if ((res = device_open(this)) < 0)
 | 
						|
			return res;
 | 
						|
 | 
						|
		if (!compress_offload_api_supports_codec(this->device_context, this->audio_codec_info.id)) {
 | 
						|
			spa_log_error(this->log, "%p: codec is not supported by the device", this);
 | 
						|
			device_close(this);
 | 
						|
			return -ENOTSUP;
 | 
						|
		}
 | 
						|
 | 
						|
		if ((res = compress_offload_api_set_params(this->device_context, &(this->audio_codec_info), 0, 0)) < 0)
 | 
						|
			return res;
 | 
						|
 | 
						|
		compress_offload_caps = compress_offload_api_get_caps(this->device_context);
 | 
						|
 | 
						|
		this->min_fragment_size = compress_offload_caps->min_fragment_size;
 | 
						|
		this->max_fragment_size = compress_offload_caps->max_fragment_size;
 | 
						|
		this->min_num_fragments = compress_offload_caps->min_fragments;
 | 
						|
		this->max_num_fragments = compress_offload_caps->max_fragments;
 | 
						|
 | 
						|
		spa_log_debug(
 | 
						|
			this->log,
 | 
						|
			"%p: min/max fragment size: %" PRIu32 "/%" PRIu32 " min/max num fragments: %" PRIu32 "/%" PRIu32,
 | 
						|
			this,
 | 
						|
			this->min_fragment_size, this->max_fragment_size,
 | 
						|
			this->min_num_fragments, this->max_num_fragments
 | 
						|
		);
 | 
						|
 | 
						|
		compress_offload_api_get_fragment_config(this->device_context,
 | 
						|
		                                         &(this->configured_fragment_size),
 | 
						|
		                                         &(this->configured_num_fragments));
 | 
						|
 | 
						|
		spa_log_debug(
 | 
						|
			this->log, "%p: configured fragment size: %" PRIu32 " configured num fragments: %" PRIu32,
 | 
						|
			this,
 | 
						|
			this->configured_fragment_size, this->configured_num_fragments
 | 
						|
		);
 | 
						|
 | 
						|
		this->current_audio_info = info;
 | 
						|
		this->have_format = true;
 | 
						|
		this->port_info.rate = SPA_FRACTION(1, rate);
 | 
						|
	}
 | 
						|
 | 
						|
	this->node_info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS;
 | 
						|
	this->node_info.flags &= ~SPA_NODE_FLAG_NEED_CONFIGURE;
 | 
						|
	emit_node_info(this, false);
 | 
						|
 | 
						|
	this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_RATE;
 | 
						|
	this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
 | 
						|
 | 
						|
	if (this->have_format) {
 | 
						|
		this->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE);
 | 
						|
		this->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ);
 | 
						|
	} else {
 | 
						|
		this->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
 | 
						|
		this->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
 | 
						|
	}
 | 
						|
 | 
						|
	emit_port_info(this, false);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int impl_port_set_param(void *object,
 | 
						|
                    enum spa_direction direction, uint32_t port_id,
 | 
						|
                    uint32_t id, uint32_t flags,
 | 
						|
                    const struct spa_pod *param)
 | 
						|
{
 | 
						|
	struct impl *this = object;
 | 
						|
	int res;
 | 
						|
 | 
						|
	spa_return_val_if_fail(this != NULL, -EINVAL);
 | 
						|
	spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
 | 
						|
 | 
						|
	switch (id) {
 | 
						|
	case SPA_PARAM_Format:
 | 
						|
		res = port_set_format(this, direction, port_id, flags, param);
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		res = -ENOENT;
 | 
						|
		break;
 | 
						|
	}
 | 
						|
	return res;
 | 
						|
}
 | 
						|
 | 
						|
static int impl_port_use_buffers(void *object,
 | 
						|
                                 enum spa_direction direction, uint32_t port_id,
 | 
						|
                                 uint32_t flags,
 | 
						|
                                 struct spa_buffer **buffers, uint32_t n_buffers)
 | 
						|
{
 | 
						|
	struct impl *this = object;
 | 
						|
	uint32_t i;
 | 
						|
 | 
						|
	spa_return_val_if_fail(this != NULL, -EINVAL);
 | 
						|
	spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
 | 
						|
 | 
						|
	if (this->n_buffers > 0) {
 | 
						|
		spa_log_debug(this->log, "%p: %u buffers currently already in use; stopping device "
 | 
						|
		              "to remove them before using new ones", this, this->n_buffers);
 | 
						|
		do_stop(this);
 | 
						|
		clear_buffers(this);
 | 
						|
	}
 | 
						|
 | 
						|
	spa_log_debug(this->log, "%p: using a pool with %d buffer(s)", this, n_buffers);
 | 
						|
 | 
						|
	if (n_buffers > 0 && !this->have_format)
 | 
						|
		return -EIO;
 | 
						|
	if (n_buffers > MAX_BUFFERS)
 | 
						|
		return -ENOSPC;
 | 
						|
 | 
						|
	for (i = 0; i < n_buffers; i++) {
 | 
						|
		struct buffer *b = &this->buffers[i];
 | 
						|
		struct spa_data *d = buffers[i]->datas;
 | 
						|
 | 
						|
		b->id = i;
 | 
						|
		b->flags = BUFFER_FLAG_AVAILABLE_FOR_NEW_DATA;
 | 
						|
		b->buf = buffers[i];
 | 
						|
 | 
						|
		if (d[0].data == NULL) {
 | 
						|
			spa_log_error(this->log, "%p: need mapped memory", this);
 | 
						|
			return -EINVAL;
 | 
						|
		}
 | 
						|
 | 
						|
		spa_log_debug(this->log, "%p: got buffer with ID %d bufptr %p data %p", this, i, b->buf, d[0].data);
 | 
						|
	}
 | 
						|
 | 
						|
	this->n_buffers = n_buffers;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int impl_port_set_io(void *object,
 | 
						|
                            enum spa_direction direction,
 | 
						|
                            uint32_t port_id,
 | 
						|
                            uint32_t id,
 | 
						|
                            void *data, size_t size)
 | 
						|
{
 | 
						|
	struct impl *this = object;
 | 
						|
 | 
						|
	spa_return_val_if_fail(this != NULL, -EINVAL);
 | 
						|
	spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
 | 
						|
 | 
						|
	switch (id) {
 | 
						|
	case SPA_IO_Buffers:
 | 
						|
		spa_log_debug(this->log, "%p: got buffers IO with data %p", this, data);
 | 
						|
		this->port_buffers_io = data;
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		return -ENOENT;
 | 
						|
	}
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int impl_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id)
 | 
						|
{
 | 
						|
	return -ENOTSUP;
 | 
						|
}
 | 
						|
 | 
						|
static int impl_node_process(void *object)
 | 
						|
{
 | 
						|
	struct impl *this = object;
 | 
						|
	struct spa_io_buffers *io;
 | 
						|
	struct buffer *b;
 | 
						|
	int res;
 | 
						|
 | 
						|
	spa_return_val_if_fail(this != NULL, -EINVAL);
 | 
						|
 | 
						|
	io = this->port_buffers_io;
 | 
						|
	spa_return_val_if_fail(io != NULL, -EIO);
 | 
						|
 | 
						|
	/* Sinks aren't supposed to actually consume anything
 | 
						|
	 * when the graph runs in freewheel mode. */
 | 
						|
	if (this->node_position_io && this->node_position_io->clock.flags & SPA_IO_CLOCK_FLAG_FREEWHEEL) {
 | 
						|
		io->status = SPA_STATUS_NEED_DATA;
 | 
						|
		return SPA_STATUS_HAVE_DATA;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Add the incoming data if there is some. We place the data in
 | 
						|
	 * a queue instead of just consuming it directly. This allows for
 | 
						|
	 * adjusting driver cycles to the needs of the sink - if the sink
 | 
						|
	 * already has data queued, it does not yet need to schedule a next
 | 
						|
	 * cycle. See on_driver_timeout() for details. This is only relevnt
 | 
						|
	 * if the sink is running as the graph's driver. */
 | 
						|
	if ((io->status == SPA_STATUS_HAVE_DATA) && (io->buffer_id < this->n_buffers)) {
 | 
						|
		b = &this->buffers[io->buffer_id];
 | 
						|
 | 
						|
		if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_AVAILABLE_FOR_NEW_DATA)) {
 | 
						|
			spa_log_warn(this->log, "%p: buffer %u in use", this, io->buffer_id);
 | 
						|
			io->status = -EINVAL;
 | 
						|
			return -EINVAL;
 | 
						|
		}
 | 
						|
 | 
						|
		if (this->device_is_paused) {
 | 
						|
			spa_log_debug(this->log, "%p: resuming paused device", this);
 | 
						|
			if ((res = device_resume(this)) < 0) {
 | 
						|
				io->status = res;
 | 
						|
				return SPA_STATUS_STOPPED;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		spa_log_trace_fp(this->log, "%p: queuing buffer %u", this, io->buffer_id);
 | 
						|
		spa_list_append(&this->queued_output_buffers, &b->link);
 | 
						|
		SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_AVAILABLE_FOR_NEW_DATA);
 | 
						|
		/* This is essential to be able to hold back this buffer
 | 
						|
		 * (which is because we queued it in a custom list for late
 | 
						|
		 * consumption). By setting buffer_id to SPA_ID_INVALID,
 | 
						|
		 * we essentially inform the graph that it must not attempt
 | 
						|
		 * to return this buffer to the buffer pool. */
 | 
						|
		io->buffer_id = SPA_ID_INVALID;
 | 
						|
 | 
						|
		if (SPA_UNLIKELY((res = write_queued_output_buffers(this)) < 0)) {
 | 
						|
			io->status = res;
 | 
						|
			return SPA_STATUS_STOPPED;
 | 
						|
		}
 | 
						|
 | 
						|
		io->status = SPA_STATUS_OK;
 | 
						|
	}
 | 
						|
 | 
						|
	return SPA_STATUS_HAVE_DATA;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/* SPA node information and init / clear procedures */
 | 
						|
 | 
						|
static const struct spa_node_methods impl_node = {
 | 
						|
	SPA_VERSION_NODE_METHODS,
 | 
						|
	.add_listener = impl_node_add_listener,
 | 
						|
	.set_callbacks = impl_node_set_callbacks,
 | 
						|
	.sync = impl_node_sync,
 | 
						|
	.enum_params = impl_node_enum_params,
 | 
						|
	.set_param = impl_node_set_param,
 | 
						|
	.set_io = impl_node_set_io,
 | 
						|
	.send_command = impl_node_send_command,
 | 
						|
	.add_port = impl_node_add_port,
 | 
						|
	.remove_port = impl_node_remove_port,
 | 
						|
	.port_enum_params = impl_port_enum_params,
 | 
						|
	.port_set_param = impl_port_set_param,
 | 
						|
	.port_use_buffers = impl_port_use_buffers,
 | 
						|
	.port_set_io = impl_port_set_io,
 | 
						|
	.port_reuse_buffer = impl_port_reuse_buffer,
 | 
						|
	.process = impl_node_process,
 | 
						|
};
 | 
						|
 | 
						|
static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
 | 
						|
{
 | 
						|
	struct impl *this;
 | 
						|
 | 
						|
	spa_return_val_if_fail(handle != NULL, -EINVAL);
 | 
						|
	spa_return_val_if_fail(interface != NULL, -EINVAL);
 | 
						|
 | 
						|
	this = (struct impl *) handle;
 | 
						|
 | 
						|
	if (spa_streq(type, SPA_TYPE_INTERFACE_Node))
 | 
						|
		*interface = &this->node;
 | 
						|
	else
 | 
						|
		return -ENOENT;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int impl_clear(struct spa_handle *handle)
 | 
						|
{
 | 
						|
	struct impl *this;
 | 
						|
 | 
						|
	spa_return_val_if_fail(handle != NULL, -EINVAL);
 | 
						|
	this = (struct impl *) handle;
 | 
						|
 | 
						|
	device_close(this);
 | 
						|
 | 
						|
	if (this->driver_timerfd > 0) {
 | 
						|
		spa_system_close(this->data_system, this->driver_timerfd);
 | 
						|
		this->driver_timerfd = -1;
 | 
						|
	}
 | 
						|
 | 
						|
	spa_log_info(this->log, "%p: created Compress-Offload sink", this);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static size_t impl_get_size(const struct spa_handle_factory *factory,
 | 
						|
	                        const struct spa_dict *params)
 | 
						|
{
 | 
						|
	return sizeof(struct impl);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle,
 | 
						|
          const struct spa_dict *info, const struct spa_support *support, uint32_t n_support)
 | 
						|
{
 | 
						|
	struct impl *this;
 | 
						|
	uint32_t i;
 | 
						|
	int res = 0;
 | 
						|
 | 
						|
	spa_return_val_if_fail(factory != NULL, -EINVAL);
 | 
						|
	spa_return_val_if_fail(handle != NULL, -EINVAL);
 | 
						|
 | 
						|
	handle->get_interface = impl_get_interface;
 | 
						|
	handle->clear = impl_clear;
 | 
						|
 | 
						|
	this = (struct impl *) handle;
 | 
						|
 | 
						|
	this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log);
 | 
						|
	/* A logger must always exist, otherwise something is very wrong. */
 | 
						|
	assert(this->log != NULL);
 | 
						|
	alsa_log_topic_init(this->log);
 | 
						|
 | 
						|
	this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop);
 | 
						|
	if (this->data_loop == NULL) {
 | 
						|
		spa_log_error(this->log, "%p: could not find a loop", this);
 | 
						|
		res = -EINVAL;
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem);
 | 
						|
	if (this->data_system == NULL) {
 | 
						|
		spa_log_error(this->log, "%p: could not find a data system", this);
 | 
						|
		res = -EINVAL;
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	this->node.iface = SPA_INTERFACE_INIT(
 | 
						|
		SPA_TYPE_INTERFACE_Node,
 | 
						|
		SPA_VERSION_NODE,
 | 
						|
		&impl_node, this);
 | 
						|
 | 
						|
	spa_hook_list_init(&this->hooks);
 | 
						|
 | 
						|
	reset_props(&this->props);
 | 
						|
 | 
						|
	this->have_format = false;
 | 
						|
 | 
						|
	this->started = false;
 | 
						|
 | 
						|
	this->freewheel = false;
 | 
						|
 | 
						|
	this->n_buffers = 0;
 | 
						|
	spa_list_init(&this->queued_output_buffers);
 | 
						|
	this->offset_within_oldest_output_buffer = 0;
 | 
						|
 | 
						|
	res = this->driver_timerfd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC,
 | 
						|
	                                                       SPA_FD_CLOEXEC | SPA_FD_NONBLOCK);
 | 
						|
	if (SPA_UNLIKELY(res < 0)) {
 | 
						|
		spa_log_error(this->log, "%p: could not create driver timerfd: %s", this, spa_strerror(res));
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	this->next_driver_time = 0;
 | 
						|
	this->following = false;
 | 
						|
 | 
						|
	this->node_info_all = SPA_NODE_CHANGE_MASK_FLAGS |
 | 
						|
	                      SPA_NODE_CHANGE_MASK_PROPS |
 | 
						|
	                      SPA_NODE_CHANGE_MASK_PARAMS;
 | 
						|
	this->node_info = SPA_NODE_INFO_INIT();
 | 
						|
	this->node_info.max_input_ports = 1;
 | 
						|
	this->node_info.flags = SPA_NODE_FLAG_RT |
 | 
						|
	                        SPA_NODE_FLAG_IN_PORT_CONFIG |
 | 
						|
	                        SPA_NODE_FLAG_NEED_CONFIGURE;
 | 
						|
	this->node_params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ);
 | 
						|
	this->node_params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE);
 | 
						|
	this->node_params[NODE_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ);
 | 
						|
	this->node_params[NODE_EnumPortConfig] = SPA_PARAM_INFO(SPA_PARAM_EnumPortConfig, SPA_PARAM_INFO_READ);
 | 
						|
	this->node_info.params = this->node_params;
 | 
						|
	this->node_info.n_params = N_NODE_PARAMS;
 | 
						|
	this->node_clock_io = NULL;
 | 
						|
	this->node_position_io = NULL;
 | 
						|
 | 
						|
	this->port_info_all = SPA_PORT_CHANGE_MASK_FLAGS |
 | 
						|
	                      SPA_PORT_CHANGE_MASK_PARAMS;
 | 
						|
	this->port_info = SPA_PORT_INFO_INIT();
 | 
						|
	this->port_info.flags = SPA_PORT_FLAG_LIVE |
 | 
						|
	                        SPA_PORT_FLAG_PHYSICAL |
 | 
						|
	                        SPA_PORT_FLAG_TERMINAL;
 | 
						|
	this->port_params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ);
 | 
						|
	this->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
 | 
						|
	this->port_params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ);
 | 
						|
	this->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
 | 
						|
	this->port_info.params = this->port_params;
 | 
						|
	this->port_info.n_params = N_PORT_PARAMS;
 | 
						|
	this->port_buffers_io = NULL;
 | 
						|
 | 
						|
	this->device_context = NULL;
 | 
						|
	this->device_started = false;
 | 
						|
	memset(&this->audio_codec_info, 0, sizeof(this->audio_codec_info));
 | 
						|
	this->device_is_paused = false;
 | 
						|
 | 
						|
	spa_log_info(this->log, "%p: initialized Compress-Offload sink", this);
 | 
						|
 | 
						|
	for (i = 0; info && i < info->n_items; i++) {
 | 
						|
		const char *k = info->items[i].key;
 | 
						|
		const char *s = info->items[i].value;
 | 
						|
		if (spa_streq(k, SPA_KEY_API_ALSA_PATH)) {
 | 
						|
			snprintf(this->props.device, sizeof(this->props.device), "%s", s);
 | 
						|
			if ((res = parse_device(this)) < 0)
 | 
						|
				return res;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
finish:
 | 
						|
	return res;
 | 
						|
 | 
						|
error:
 | 
						|
	impl_clear((struct spa_handle *)this);
 | 
						|
	goto finish;
 | 
						|
}
 | 
						|
 | 
						|
static const struct spa_interface_info impl_interfaces[] = {
 | 
						|
	{SPA_TYPE_INTERFACE_Node,},
 | 
						|
};
 | 
						|
 | 
						|
static int
 | 
						|
impl_enum_interface_info(const struct spa_handle_factory *factory,
 | 
						|
                         const struct spa_interface_info **info, uint32_t *index)
 | 
						|
{
 | 
						|
	spa_return_val_if_fail(factory != NULL, -EINVAL);
 | 
						|
	spa_return_val_if_fail(info != NULL, -EINVAL);
 | 
						|
	spa_return_val_if_fail(index != NULL, -EINVAL);
 | 
						|
 | 
						|
	switch (*index) {
 | 
						|
	case 0:
 | 
						|
		*info = &impl_interfaces[*index];
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	(*index)++;
 | 
						|
	return 1;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
/* Factory info */
 | 
						|
 | 
						|
static const struct spa_dict_item info_items[] = {
 | 
						|
	{ SPA_KEY_FACTORY_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io>, Carlos Rafael Giani <crg7475@mailbox.org>" },
 | 
						|
	{ SPA_KEY_FACTORY_DESCRIPTION, "Play compressed audio (like MP3 or AAC) with the ALSA Compress-Offload API" },
 | 
						|
	{ SPA_KEY_FACTORY_USAGE, "["SPA_KEY_API_ALSA_PATH"=<path>]" },
 | 
						|
};
 | 
						|
 | 
						|
static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items);
 | 
						|
 | 
						|
const struct spa_handle_factory spa_alsa_compress_offload_sink_factory = {
 | 
						|
	SPA_VERSION_HANDLE_FACTORY,
 | 
						|
	SPA_NAME_API_ALSA_COMPRESS_OFFLOAD_SINK,
 | 
						|
	&info,
 | 
						|
	impl_get_size,
 | 
						|
	impl_init,
 | 
						|
	impl_enum_interface_info,
 | 
						|
};
 |