mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-12-16 08:56:45 -05:00
alsa-compress-offload-sink: major sink rework
* Add support for running the sink as a driver * Detect which compressed formats are actually supported * Correctly open/close/start/stop device according to the node commands * Shift away from tinycompress and use Compress-Offload ioctls directly to be able to access various caps information (including fragment sizes) which are unavailable in the tinycompress API * Implement SPA_PARAM_PropInfo and SPA_PARAM_Props support
This commit is contained in:
parent
f03c606ad9
commit
031f992981
6 changed files with 1941 additions and 695 deletions
|
|
@ -284,7 +284,7 @@ option('gsettings',
|
||||||
option('compress-offload',
|
option('compress-offload',
|
||||||
description: 'Enable ALSA Compress-Offload support',
|
description: 'Enable ALSA Compress-Offload support',
|
||||||
type: 'feature',
|
type: 'feature',
|
||||||
value: 'disabled')
|
value: 'auto')
|
||||||
option('pam-defaults-install',
|
option('pam-defaults-install',
|
||||||
description: 'Install limits.d file modifying defaults for all PAM users. Only for old kernels/systemd!',
|
description: 'Install limits.d file modifying defaults for all PAM users. Only for old kernels/systemd!',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
|
|
||||||
|
|
@ -92,9 +92,9 @@ if get_option('spa-plugins').allowed()
|
||||||
libcamera_dep = dependency('libcamera', required: get_option('libcamera'))
|
libcamera_dep = dependency('libcamera', required: get_option('libcamera'))
|
||||||
summary({'libcamera': libcamera_dep.found()}, bool_yn: true, section: 'Backend')
|
summary({'libcamera': libcamera_dep.found()}, bool_yn: true, section: 'Backend')
|
||||||
|
|
||||||
tinycompress_dep = cc.find_library('tinycompress', has_headers: ['tinycompress/tinycompress.h' ], required: get_option('compress-offload'))
|
compress_offload_option = get_option('compress-offload')
|
||||||
summary({'Compress-Offload': tinycompress_dep.found()}, bool_yn: true, section: 'Backend')
|
summary({'Compress-Offload': compress_offload_option.allowed()}, bool_yn: true, section: 'Backend')
|
||||||
cdata.set('HAVE_ALSA_COMPRESS_OFFLOAD', tinycompress_dep.found())
|
cdata.set('HAVE_ALSA_COMPRESS_OFFLOAD', compress_offload_option.allowed())
|
||||||
|
|
||||||
# common dependencies
|
# common dependencies
|
||||||
libudev_dep = dependency('libudev', required: alsa_dep.found() or get_option('udev').enabled() or get_option('v4l2').enabled())
|
libudev_dep = dependency('libudev', required: alsa_dep.found() or get_option('udev').enabled() or get_option('v4l2').enabled())
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
273
spa/plugins/alsa/compress-offload-api.c
Normal file
273
spa/plugins/alsa/compress-offload-api.c
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include "compress-offload-api.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct compress_offload_api_context {
|
||||||
|
int fd;
|
||||||
|
struct snd_compr_caps caps;
|
||||||
|
struct spa_log *log;
|
||||||
|
bool was_configured;
|
||||||
|
uint32_t fragment_size;
|
||||||
|
uint32_t num_fragments;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct compress_offload_api_context* compress_offload_api_open(int card_nr, int device_nr, struct spa_log *log)
|
||||||
|
{
|
||||||
|
struct compress_offload_api_context *context;
|
||||||
|
char fn[256];
|
||||||
|
|
||||||
|
assert(card_nr >= 0);
|
||||||
|
assert(device_nr >= 0);
|
||||||
|
assert(log != NULL);
|
||||||
|
|
||||||
|
context = calloc(1, sizeof(struct compress_offload_api_context));
|
||||||
|
if (context == NULL) {
|
||||||
|
errno = ENOMEM;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
context->log = log;
|
||||||
|
|
||||||
|
snprintf(fn, sizeof(fn), "/dev/snd/comprC%uD%u", card_nr, device_nr);
|
||||||
|
|
||||||
|
context->fd = open(fn, O_WRONLY);
|
||||||
|
if (context->fd < 0) {
|
||||||
|
spa_log_error(context->log, "could not open device \"%s\": %s (%d)", fn, strerror(errno), errno);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ioctl(context->fd, SNDRV_COMPRESS_GET_CAPS, &(context->caps)) != 0) {
|
||||||
|
spa_log_error(context->log, "could not get device caps: %s (%d)", strerror(errno), errno);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
|
||||||
|
error:
|
||||||
|
compress_offload_api_close(context);
|
||||||
|
if (errno == 0)
|
||||||
|
errno = EIO;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void compress_offload_api_close(struct compress_offload_api_context *context)
|
||||||
|
{
|
||||||
|
if (context == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (context->fd > 0)
|
||||||
|
close(context->fd);
|
||||||
|
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int compress_offload_api_get_fd(struct compress_offload_api_context *context)
|
||||||
|
{
|
||||||
|
assert(context != NULL);
|
||||||
|
return context->fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int compress_offload_api_set_params(struct compress_offload_api_context *context, struct snd_codec *codec,
|
||||||
|
uint32_t fragment_size, uint32_t num_fragments)
|
||||||
|
{
|
||||||
|
struct snd_compr_params params;
|
||||||
|
|
||||||
|
assert(context != NULL);
|
||||||
|
assert(codec != NULL);
|
||||||
|
assert(
|
||||||
|
(fragment_size == 0) ||
|
||||||
|
((fragment_size >= context->caps.min_fragment_size) && (fragment_size <= context->caps.max_fragment_size))
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
(num_fragments == 0) ||
|
||||||
|
((num_fragments >= context->caps.min_fragments) && (fragment_size <= context->caps.max_fragments))
|
||||||
|
);
|
||||||
|
|
||||||
|
context->fragment_size = (fragment_size != 0) ? fragment_size : context->caps.min_fragment_size;
|
||||||
|
context->num_fragments = (num_fragments != 0) ? num_fragments : context->caps.max_fragments;
|
||||||
|
|
||||||
|
memset(¶ms, 0, sizeof(params));
|
||||||
|
params.buffer.fragment_size = context->fragment_size;
|
||||||
|
params.buffer.fragments = context->num_fragments;
|
||||||
|
memcpy(&(params.codec), codec, sizeof(struct snd_codec));
|
||||||
|
|
||||||
|
if (ioctl(context->fd, SNDRV_COMPRESS_SET_PARAMS, ¶ms) != 0) {
|
||||||
|
spa_log_error(context->log, "could not set params: %s (%d)", strerror(errno), errno);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
context->was_configured = true;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void compress_offload_api_get_fragment_config(struct compress_offload_api_context *context,
|
||||||
|
uint32_t *fragment_size, uint32_t *num_fragments)
|
||||||
|
{
|
||||||
|
assert(context != NULL);
|
||||||
|
assert(fragment_size != NULL);
|
||||||
|
assert(num_fragments != NULL);
|
||||||
|
|
||||||
|
*fragment_size = context->fragment_size;
|
||||||
|
*num_fragments = context->num_fragments;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const struct snd_compr_caps * compress_offload_api_get_caps(struct compress_offload_api_context *context)
|
||||||
|
{
|
||||||
|
assert(context != NULL);
|
||||||
|
return &(context->caps);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int compress_offload_api_get_codec_caps(struct compress_offload_api_context *context,
|
||||||
|
uint32_t codec_id, struct snd_compr_codec_caps *codec_caps)
|
||||||
|
{
|
||||||
|
assert(context != NULL);
|
||||||
|
assert(codec_id < SND_AUDIOCODEC_MAX);
|
||||||
|
assert(codec_caps != NULL);
|
||||||
|
|
||||||
|
memset(codec_caps, 0, sizeof(struct snd_compr_codec_caps));
|
||||||
|
codec_caps->codec = codec_id;
|
||||||
|
|
||||||
|
if (ioctl(context->fd, SNDRV_COMPRESS_GET_CODEC_CAPS, codec_caps) != 0) {
|
||||||
|
spa_log_error(context->log, "could not get caps for codec with ID %#08x: %s (%d)",
|
||||||
|
codec_id, strerror(errno), errno);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool compress_offload_api_supports_codec(struct compress_offload_api_context *context, uint32_t codec_id)
|
||||||
|
{
|
||||||
|
uint32_t codec_index;
|
||||||
|
|
||||||
|
assert(context != NULL);
|
||||||
|
assert(codec_id < SND_AUDIOCODEC_MAX);
|
||||||
|
|
||||||
|
for (codec_index = 0; codec_index < context->caps.num_codecs; ++codec_index) {
|
||||||
|
if (context->caps.codecs[codec_index] == codec_id)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define RUN_SIMPLE_COMMAND(CONTEXT, CMD, CMD_NAME) \
|
||||||
|
{ \
|
||||||
|
assert((CONTEXT) != NULL); \
|
||||||
|
assert((CMD_NAME) != NULL); \
|
||||||
|
\
|
||||||
|
if (ioctl((CONTEXT)->fd, (CMD)) < 0) { \
|
||||||
|
spa_log_error((CONTEXT)->log, "could not %s device: %s (%d)", (CMD_NAME), strerror(errno), errno); \
|
||||||
|
return -errno; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
return 0; \
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int compress_offload_api_start(struct compress_offload_api_context *context)
|
||||||
|
{
|
||||||
|
RUN_SIMPLE_COMMAND(context, SNDRV_COMPRESS_START, "start");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int compress_offload_api_stop(struct compress_offload_api_context *context)
|
||||||
|
{
|
||||||
|
RUN_SIMPLE_COMMAND(context, SNDRV_COMPRESS_STOP, "stop");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int compress_offload_api_pause(struct compress_offload_api_context *context)
|
||||||
|
{
|
||||||
|
RUN_SIMPLE_COMMAND(context, SNDRV_COMPRESS_PAUSE, "pause");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int compress_offload_api_resume(struct compress_offload_api_context *context)
|
||||||
|
{
|
||||||
|
RUN_SIMPLE_COMMAND(context, SNDRV_COMPRESS_RESUME, "resume");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int compress_offload_api_drain(struct compress_offload_api_context *context)
|
||||||
|
{
|
||||||
|
RUN_SIMPLE_COMMAND(context, SNDRV_COMPRESS_DRAIN, "drain");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int compress_offload_api_get_timestamp(struct compress_offload_api_context *context,
|
||||||
|
struct snd_compr_tstamp *timestamp)
|
||||||
|
{
|
||||||
|
assert(context != NULL);
|
||||||
|
assert(timestamp != NULL);
|
||||||
|
|
||||||
|
if (ioctl(context->fd, SNDRV_COMPRESS_TSTAMP, timestamp) < 0) {
|
||||||
|
spa_log_error(context->log, "could not get timestamp device: %s (%d)",
|
||||||
|
strerror(errno), errno);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int compress_offload_api_get_available_space(struct compress_offload_api_context *context,
|
||||||
|
struct snd_compr_avail *available_space)
|
||||||
|
{
|
||||||
|
assert(context != NULL);
|
||||||
|
assert(available_space != NULL);
|
||||||
|
|
||||||
|
if (ioctl(context->fd, SNDRV_COMPRESS_AVAIL, available_space) < 0) {
|
||||||
|
spa_log_error(context->log, "could not get available space from device: %s (%d)",
|
||||||
|
strerror(errno), errno);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int compress_offload_api_write(struct compress_offload_api_context *context, const void *data, size_t size)
|
||||||
|
{
|
||||||
|
int num_bytes_written;
|
||||||
|
|
||||||
|
assert(context != NULL);
|
||||||
|
assert(data != NULL);
|
||||||
|
|
||||||
|
num_bytes_written = write(context->fd, data, size);
|
||||||
|
if (num_bytes_written < 0) {
|
||||||
|
switch (errno) {
|
||||||
|
case EBADFD:
|
||||||
|
/* EBADFD indicates that the device is paused and thus is not an error. */
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
spa_log_error(context->log, "could not write %zu byte(s): %s (%d)",
|
||||||
|
size, strerror(errno), errno);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
return num_bytes_written;
|
||||||
|
}
|
||||||
76
spa/plugins/alsa/compress-offload-api.h
Normal file
76
spa/plugins/alsa/compress-offload-api.h
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#ifndef COMPRESS_OFFLOAD_API_H
|
||||||
|
#define COMPRESS_OFFLOAD_API_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <sound/compress_offload.h>
|
||||||
|
#include <sound/compress_params.h>
|
||||||
|
#include <spa/support/log.h>
|
||||||
|
|
||||||
|
|
||||||
|
struct compress_offload_api_context;
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(__GNUC__) && __GNUC__ >= 4
|
||||||
|
#define COMPR_API_PRIVATE __attribute__((visibility("hidden")))
|
||||||
|
#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590)
|
||||||
|
#define COMPR_API_PRIVATE __attribute__((visibility("hidden")))
|
||||||
|
#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x550)
|
||||||
|
#define COMPR_API_PRIVATE __hidden
|
||||||
|
#else
|
||||||
|
#define COMPR_API_PRIVATE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* This is a simple encapsulation of the ALSA Compress-Offload API
|
||||||
|
* and its ioctl calls. It is intentionally not using any PipeWire
|
||||||
|
* or SPA headers to allow for porting it or extracting it as its
|
||||||
|
* own library in the future if needed. It functions as an alternative
|
||||||
|
* to tinycompress, and was written, because tinycompress lacks
|
||||||
|
* critical functionality (it does not expose important device caps)
|
||||||
|
* and adds little value in this particular use case.
|
||||||
|
*
|
||||||
|
* Encapsulating the ioctls behind this API also allows for using
|
||||||
|
* different backends. This might be interesting in the future for
|
||||||
|
* testing purposes; for example, an alternative backend could exist
|
||||||
|
* that emulates a compress-offload device by decoding with FFmpeg.
|
||||||
|
* This would be useful for debugging compressed audio related issues
|
||||||
|
* in PipeWire on the PC - an important advantage, since getting to
|
||||||
|
* actual compress-offload hardware can sometimes be difficult. */
|
||||||
|
|
||||||
|
|
||||||
|
COMPR_API_PRIVATE struct compress_offload_api_context* compress_offload_api_open(int card_nr, int device_nr,
|
||||||
|
struct spa_log *log);
|
||||||
|
COMPR_API_PRIVATE void compress_offload_api_close(struct compress_offload_api_context *context);
|
||||||
|
|
||||||
|
COMPR_API_PRIVATE int compress_offload_api_get_fd(struct compress_offload_api_context *context);
|
||||||
|
|
||||||
|
COMPR_API_PRIVATE int compress_offload_api_set_params(struct compress_offload_api_context *context,
|
||||||
|
struct snd_codec *codec, uint32_t fragment_size,
|
||||||
|
uint32_t num_fragments);
|
||||||
|
COMPR_API_PRIVATE void compress_offload_api_get_fragment_config(struct compress_offload_api_context *context,
|
||||||
|
uint32_t *fragment_size, uint32_t *num_fragments);
|
||||||
|
|
||||||
|
COMPR_API_PRIVATE const struct snd_compr_caps * compress_offload_api_get_caps(struct compress_offload_api_context *context);
|
||||||
|
COMPR_API_PRIVATE int compress_offload_api_get_codec_caps(struct compress_offload_api_context *context,
|
||||||
|
uint32_t codec_id, struct snd_compr_codec_caps *codec_caps);
|
||||||
|
COMPR_API_PRIVATE bool compress_offload_api_supports_codec(struct compress_offload_api_context *context, uint32_t codec_id);
|
||||||
|
|
||||||
|
COMPR_API_PRIVATE int compress_offload_api_start(struct compress_offload_api_context *context);
|
||||||
|
COMPR_API_PRIVATE int compress_offload_api_stop(struct compress_offload_api_context *context);
|
||||||
|
|
||||||
|
COMPR_API_PRIVATE int compress_offload_api_pause(struct compress_offload_api_context *context);
|
||||||
|
COMPR_API_PRIVATE int compress_offload_api_resume(struct compress_offload_api_context *context);
|
||||||
|
|
||||||
|
COMPR_API_PRIVATE int compress_offload_api_drain(struct compress_offload_api_context *context);
|
||||||
|
|
||||||
|
COMPR_API_PRIVATE int compress_offload_api_get_timestamp(struct compress_offload_api_context *context,
|
||||||
|
struct snd_compr_tstamp *timestamp);
|
||||||
|
COMPR_API_PRIVATE int compress_offload_api_get_available_space(struct compress_offload_api_context *context,
|
||||||
|
struct snd_compr_avail *available_space);
|
||||||
|
|
||||||
|
COMPR_API_PRIVATE int compress_offload_api_write(struct compress_offload_api_context *context,
|
||||||
|
const void *data, size_t size);
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* COMPRESS_OFFLOAD_API_H */
|
||||||
|
|
@ -14,9 +14,8 @@ spa_alsa_sources = ['alsa.c',
|
||||||
'alsa-seq-bridge.c',
|
'alsa-seq-bridge.c',
|
||||||
'alsa-seq.c']
|
'alsa-seq.c']
|
||||||
|
|
||||||
if tinycompress_dep.found()
|
if compress_offload_option.allowed()
|
||||||
spa_alsa_sources += [ 'alsa-compress-offload-sink.c' ]
|
spa_alsa_sources += [ 'alsa-compress-offload-sink.c', 'compress-offload-api.c' ]
|
||||||
spa_alsa_dependencies += tinycompress_dep
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
spa_alsa = shared_library(
|
spa_alsa = shared_library(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue