mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
start of AVB support
This commit is contained in:
parent
957e3a7b38
commit
1adc94df11
10 changed files with 2050 additions and 0 deletions
|
|
@ -245,3 +245,7 @@ option('legacy-rtkit',
|
|||
description: 'Build legacy rtkit module',
|
||||
type: 'boolean',
|
||||
value: 'true')
|
||||
option('avb',
|
||||
description: 'Enable AVB code',
|
||||
type: 'feature',
|
||||
value: 'auto')
|
||||
|
|
|
|||
968
spa/plugins/avb/avb-pcm-sink.c
Normal file
968
spa/plugins/avb/avb-pcm-sink.c
Normal file
|
|
@ -0,0 +1,968 @@
|
|||
/* Spa AVB PCM Sink
|
||||
*
|
||||
* Copyright © 2022 Wim Taymans
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <avtp.h>
|
||||
#include <avtp_aaf.h>
|
||||
|
||||
#include <spa/node/node.h>
|
||||
#include <spa/node/utils.h>
|
||||
#include <spa/node/keys.h>
|
||||
#include <spa/monitor/device.h>
|
||||
#include <spa/utils/keys.h>
|
||||
#include <spa/utils/names.h>
|
||||
#include <spa/utils/string.h>
|
||||
#include <spa/param/audio/format.h>
|
||||
#include <spa/pod/filter.h>
|
||||
#include <spa/debug/pod.h>
|
||||
|
||||
#include "avb-pcm.h"
|
||||
|
||||
#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0)
|
||||
#define GET_PORT(this,d,p) (&this->ports[p])
|
||||
|
||||
static void reset_props(struct props *props)
|
||||
{
|
||||
}
|
||||
|
||||
static void emit_node_info(struct state *this, bool full)
|
||||
{
|
||||
uint64_t old = full ? this->info.change_mask : 0;
|
||||
|
||||
if (full)
|
||||
this->info.change_mask = this->info_all;
|
||||
if (this->info.change_mask) {
|
||||
struct spa_dict_item items[4];
|
||||
uint32_t i, n_items = 0;
|
||||
|
||||
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "avb");
|
||||
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Sink");
|
||||
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true");
|
||||
this->info.props = &SPA_DICT_INIT(items, n_items);
|
||||
|
||||
if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) {
|
||||
for (i = 0; i < this->info.n_params; i++) {
|
||||
if (this->params[i].user > 0) {
|
||||
this->params[i].flags ^= SPA_PARAM_INFO_SERIAL;
|
||||
this->params[i].user = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
spa_node_emit_info(&this->hooks, &this->info);
|
||||
|
||||
this->info.change_mask = old;
|
||||
}
|
||||
}
|
||||
|
||||
static void emit_port_info(struct state *this, struct port *port, bool full)
|
||||
{
|
||||
uint64_t old = full ? port->info.change_mask : 0;
|
||||
|
||||
if (full)
|
||||
port->info.change_mask = port->info_all;
|
||||
if (port->info.change_mask) {
|
||||
uint32_t i;
|
||||
|
||||
if (port->info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) {
|
||||
for (i = 0; i < port->info.n_params; i++) {
|
||||
if (port->params[i].user > 0) {
|
||||
port->params[i].flags ^= SPA_PARAM_INFO_SERIAL;
|
||||
port->params[i].user = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
spa_node_emit_port_info(&this->hooks,
|
||||
port->direction, port->id, &port->info);
|
||||
port->info.change_mask = old;
|
||||
}
|
||||
}
|
||||
|
||||
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 state *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_name, SPA_POD_String("avb.ifname"),
|
||||
SPA_PROP_INFO_description, SPA_POD_String("The AVB interface name"),
|
||||
SPA_PROP_INFO_type, SPA_POD_Stringn(p->ifname, sizeof(p->ifname)),
|
||||
SPA_PROP_INFO_params, SPA_POD_Bool(true));
|
||||
break;
|
||||
case 1:
|
||||
param = spa_pod_builder_add_object(&b,
|
||||
SPA_TYPE_OBJECT_PropInfo, id,
|
||||
SPA_PROP_INFO_name, SPA_POD_String("avb.macaddr"),
|
||||
SPA_PROP_INFO_description, SPA_POD_String("The AVB MAC address"),
|
||||
SPA_PROP_INFO_type, SPA_POD_Stringn(p->addr, sizeof(p->addr)),
|
||||
SPA_PROP_INFO_params, SPA_POD_Bool(true));
|
||||
break;
|
||||
case 2:
|
||||
param = spa_pod_builder_add_object(&b,
|
||||
SPA_TYPE_OBJECT_PropInfo, id,
|
||||
SPA_PROP_INFO_name, SPA_POD_String("avb.prio"),
|
||||
SPA_PROP_INFO_description, SPA_POD_String("The AVB stream priority"),
|
||||
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->prio, 0, INT32_MAX),
|
||||
SPA_PROP_INFO_params, SPA_POD_Bool(true));
|
||||
break;
|
||||
case 3:
|
||||
param = spa_pod_builder_add_object(&b,
|
||||
SPA_TYPE_OBJECT_PropInfo, id,
|
||||
SPA_PROP_INFO_name, SPA_POD_String("avb.streamid"),
|
||||
SPA_PROP_INFO_description, SPA_POD_String("The AVB stream id"),
|
||||
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Long(p->streamid, 0LL, UINT64_MAX),
|
||||
SPA_PROP_INFO_params, SPA_POD_Bool(true));
|
||||
break;
|
||||
case 4:
|
||||
param = spa_pod_builder_add_object(&b,
|
||||
SPA_TYPE_OBJECT_PropInfo, id,
|
||||
SPA_PROP_INFO_name, SPA_POD_String("avb.mtt"),
|
||||
SPA_PROP_INFO_description, SPA_POD_String("The AVB mtt"),
|
||||
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->mtt, 0, INT32_MAX),
|
||||
SPA_PROP_INFO_params, SPA_POD_Bool(true));
|
||||
break;
|
||||
case 5:
|
||||
param = spa_pod_builder_add_object(&b,
|
||||
SPA_TYPE_OBJECT_PropInfo, id,
|
||||
SPA_PROP_INFO_name, SPA_POD_String("avb.time-uncertainty"),
|
||||
SPA_PROP_INFO_description, SPA_POD_String("The AVB time uncertainty"),
|
||||
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->t_uncertainty, 0, INT32_MAX),
|
||||
SPA_PROP_INFO_params, SPA_POD_Bool(true));
|
||||
break;
|
||||
case 6:
|
||||
param = spa_pod_builder_add_object(&b,
|
||||
SPA_TYPE_OBJECT_PropInfo, id,
|
||||
SPA_PROP_INFO_name, SPA_POD_String("avb.frames-per-pdu"),
|
||||
SPA_PROP_INFO_description, SPA_POD_String("The AVB frames per packet"),
|
||||
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->frames_per_pdu, 0, INT32_MAX),
|
||||
SPA_PROP_INFO_params, SPA_POD_Bool(true));
|
||||
break;
|
||||
case 7:
|
||||
param = spa_pod_builder_add_object(&b,
|
||||
SPA_TYPE_OBJECT_PropInfo, id,
|
||||
SPA_PROP_INFO_name, SPA_POD_String("avb.ptime-tolerance"),
|
||||
SPA_PROP_INFO_description, SPA_POD_String("The AVB packet tolerance"),
|
||||
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->ptime_tolerance, 0, INT32_MAX),
|
||||
SPA_PROP_INFO_params, SPA_POD_Bool(true));
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SPA_PARAM_Props:
|
||||
{
|
||||
struct spa_pod_frame f;
|
||||
|
||||
switch (result.index) {
|
||||
case 0:
|
||||
spa_pod_builder_push_object(&b, &f,
|
||||
SPA_TYPE_OBJECT_Props, id);
|
||||
spa_pod_builder_add(&b,
|
||||
SPA_PROP_latencyOffsetNsec, SPA_POD_Long(this->process_latency.ns),
|
||||
0);
|
||||
spa_avb_add_prop_params(this, &b);
|
||||
param = spa_pod_builder_pop(&b, &f);
|
||||
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_ProcessLatency:
|
||||
switch (result.index) {
|
||||
case 0:
|
||||
param = spa_process_latency_build(&b, id, &this->process_latency);
|
||||
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_io(void *object, uint32_t id, void *data, size_t size)
|
||||
{
|
||||
struct state *this = object;
|
||||
|
||||
spa_return_val_if_fail(this != NULL, -EINVAL);
|
||||
|
||||
switch (id) {
|
||||
case SPA_IO_Clock:
|
||||
this->clock = data;
|
||||
break;
|
||||
case SPA_IO_Position:
|
||||
this->position = data;
|
||||
break;
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
spa_avb_reassign_follower(this);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void handle_process_latency(struct state *this,
|
||||
const struct spa_process_latency_info *info)
|
||||
{
|
||||
bool ns_changed = this->process_latency.ns != info->ns;
|
||||
struct port *port = &this->ports[0];
|
||||
|
||||
if (this->process_latency.quantum == info->quantum &&
|
||||
this->process_latency.rate == info->rate &&
|
||||
!ns_changed)
|
||||
return;
|
||||
|
||||
this->process_latency = *info;
|
||||
|
||||
this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS;
|
||||
if (ns_changed)
|
||||
this->params[NODE_Props].user++;
|
||||
this->params[NODE_ProcessLatency].user++;
|
||||
|
||||
port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
|
||||
port->params[PORT_Latency].user++;
|
||||
}
|
||||
|
||||
static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
|
||||
const struct spa_pod *param)
|
||||
{
|
||||
struct state *this = object;
|
||||
int res;
|
||||
|
||||
spa_return_val_if_fail(this != NULL, -EINVAL);
|
||||
|
||||
switch (id) {
|
||||
case SPA_PARAM_Props:
|
||||
{
|
||||
struct props *p = &this->props;
|
||||
struct spa_pod *params = NULL;
|
||||
int64_t lat_ns = -1;
|
||||
|
||||
if (param == NULL) {
|
||||
reset_props(p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
spa_pod_parse_object(param,
|
||||
SPA_TYPE_OBJECT_Props, NULL,
|
||||
SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&lat_ns),
|
||||
SPA_PROP_params, SPA_POD_OPT_Pod(¶ms));
|
||||
|
||||
spa_avb_parse_prop_params(this, params);
|
||||
if (lat_ns != -1) {
|
||||
struct spa_process_latency_info info;
|
||||
info = this->process_latency;
|
||||
info.ns = lat_ns;
|
||||
handle_process_latency(this, &info);
|
||||
}
|
||||
emit_node_info(this, false);
|
||||
emit_port_info(this, &this->ports[0], false);
|
||||
break;
|
||||
}
|
||||
case SPA_PARAM_ProcessLatency:
|
||||
{
|
||||
struct spa_process_latency_info info;
|
||||
if ((res = spa_process_latency_parse(param, &info)) < 0)
|
||||
return res;
|
||||
|
||||
handle_process_latency(this, &info);
|
||||
|
||||
emit_node_info(this, false);
|
||||
emit_port_info(this, &this->ports[0], false);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int impl_node_send_command(void *object, const struct spa_command *command)
|
||||
{
|
||||
struct state *this = object;
|
||||
int res;
|
||||
|
||||
spa_return_val_if_fail(this != NULL, -EINVAL);
|
||||
spa_return_val_if_fail(command != NULL, -EINVAL);
|
||||
|
||||
switch (SPA_NODE_COMMAND_ID(command)) {
|
||||
case SPA_NODE_COMMAND_ParamBegin:
|
||||
break;
|
||||
case SPA_NODE_COMMAND_ParamEnd:
|
||||
break;
|
||||
case SPA_NODE_COMMAND_Start:
|
||||
if (!this->ports[0].have_format)
|
||||
return -EIO;
|
||||
if (this->ports[0].n_buffers == 0)
|
||||
return -EIO;
|
||||
if ((res = spa_avb_start(this)) < 0)
|
||||
return res;
|
||||
break;
|
||||
case SPA_NODE_COMMAND_Suspend:
|
||||
case SPA_NODE_COMMAND_Pause:
|
||||
if ((res = spa_avb_pause(this)) < 0)
|
||||
return res;
|
||||
break;
|
||||
default:
|
||||
return -ENOTSUP;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
impl_node_add_listener(void *object,
|
||||
struct spa_hook *listener,
|
||||
const struct spa_node_events *events,
|
||||
void *data)
|
||||
{
|
||||
struct state *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, &this->ports[0], 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 state *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 state *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_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
|
||||
impl_node_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 state *this = object;
|
||||
struct spa_pod *param;
|
||||
struct spa_pod_builder b = { 0 };
|
||||
uint8_t buffer[1024];
|
||||
struct spa_result_node_params result;
|
||||
uint32_t count = 0;
|
||||
struct port *port;
|
||||
|
||||
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);
|
||||
|
||||
port = GET_PORT(this, direction, port_id);
|
||||
|
||||
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 spa_avb_enum_format(this, seq, start, num, filter);
|
||||
|
||||
case SPA_PARAM_Format:
|
||||
if (!port->have_format)
|
||||
return -EIO;
|
||||
if (result.index > 0)
|
||||
return 0;
|
||||
|
||||
param = spa_format_audio_raw_build(&b, id,
|
||||
&port->current_format.info.raw);
|
||||
break;
|
||||
|
||||
case SPA_PARAM_Buffers:
|
||||
if (!port->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(2, 1, MAX_BUFFERS),
|
||||
SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(this->blocks),
|
||||
SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(
|
||||
this->quantum_limit * this->frame_size,
|
||||
16 * this->frame_size,
|
||||
INT32_MAX),
|
||||
SPA_PARAM_BUFFERS_stride, SPA_POD_Int(this->frame_size));
|
||||
break;
|
||||
|
||||
case SPA_PARAM_Meta:
|
||||
switch (result.index) {
|
||||
case 0:
|
||||
param = spa_pod_builder_add_object(&b,
|
||||
SPA_TYPE_OBJECT_ParamMeta, id,
|
||||
SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
|
||||
SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header)));
|
||||
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_Buffers),
|
||||
SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers)));
|
||||
break;
|
||||
case 1:
|
||||
param = spa_pod_builder_add_object(&b,
|
||||
SPA_TYPE_OBJECT_ParamIO, id,
|
||||
SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_RateMatch),
|
||||
SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_rate_match)));
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case SPA_PARAM_Latency:
|
||||
switch (result.index) {
|
||||
case 0: case 1:
|
||||
{
|
||||
struct spa_latency_info latency = this->latency[result.index];
|
||||
if (latency.direction == SPA_DIRECTION_INPUT)
|
||||
spa_process_latency_info_add(&this->process_latency, &latency);
|
||||
param = spa_latency_build(&b, id, &latency);
|
||||
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 clear_buffers(struct state *this, struct port *port)
|
||||
{
|
||||
if (port->n_buffers > 0) {
|
||||
spa_list_init(&port->ready);
|
||||
port->n_buffers = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int port_set_format(void *object, struct port *port,
|
||||
uint32_t flags, const struct spa_pod *format)
|
||||
{
|
||||
struct state *this = object;
|
||||
int err;
|
||||
|
||||
if (format == NULL) {
|
||||
if (!port->have_format)
|
||||
return 0;
|
||||
|
||||
spa_log_debug(this->log, "clear format");
|
||||
clear_buffers(this, port);
|
||||
} else {
|
||||
struct spa_audio_info info = { 0 };
|
||||
|
||||
if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0)
|
||||
return err;
|
||||
|
||||
if (info.media_type != SPA_MEDIA_TYPE_audio ||
|
||||
info.media_subtype != SPA_MEDIA_SUBTYPE_raw)
|
||||
return -EINVAL;
|
||||
|
||||
if (spa_format_audio_raw_parse(format, &info.info.raw) < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if ((err = spa_avb_set_format(this, &info, flags)) < 0)
|
||||
return err;
|
||||
|
||||
port->current_format = info;
|
||||
}
|
||||
|
||||
this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS;
|
||||
emit_node_info(this, false);
|
||||
|
||||
port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE;
|
||||
port->info.rate = SPA_FRACTION(1, this->rate);
|
||||
port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
|
||||
if (port->have_format) {
|
||||
port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE);
|
||||
port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ);
|
||||
port->params[PORT_Latency].user++;
|
||||
} else {
|
||||
port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
|
||||
port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
|
||||
}
|
||||
emit_port_info(this, port, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
impl_node_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 state *this = object;
|
||||
struct port *port;
|
||||
int res;
|
||||
|
||||
spa_return_val_if_fail(this != NULL, -EINVAL);
|
||||
|
||||
spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
|
||||
|
||||
port = GET_PORT(this, direction, port_id);
|
||||
|
||||
switch (id) {
|
||||
case SPA_PARAM_Format:
|
||||
res = port_set_format(this, port, flags, param);
|
||||
break;
|
||||
case SPA_PARAM_Latency:
|
||||
{
|
||||
struct spa_latency_info info;
|
||||
if ((res = spa_latency_parse(param, &info)) < 0)
|
||||
return res;
|
||||
if (direction == info.direction)
|
||||
return -EINVAL;
|
||||
|
||||
this->latency[info.direction] = info;
|
||||
port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
|
||||
port->params[PORT_Latency].user++;
|
||||
emit_port_info(this, port, false);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
res = -ENOENT;
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int
|
||||
impl_node_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 state *this = object;
|
||||
struct port *port;
|
||||
uint32_t i;
|
||||
|
||||
spa_return_val_if_fail(this != NULL, -EINVAL);
|
||||
|
||||
spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
|
||||
|
||||
port = GET_PORT(this, direction, port_id);
|
||||
|
||||
spa_log_debug(this->log, "%p: use %d buffers", this, n_buffers);
|
||||
|
||||
if (!port->have_format)
|
||||
return -EIO;
|
||||
|
||||
if (n_buffers == 0) {
|
||||
spa_avb_pause(this);
|
||||
clear_buffers(this, port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < n_buffers; i++) {
|
||||
struct buffer *b = &port->buffers[i];
|
||||
struct spa_data *d = buffers[i]->datas;
|
||||
|
||||
b->buf = buffers[i];
|
||||
b->id = i;
|
||||
b->flags = BUFFER_FLAG_OUT;
|
||||
|
||||
b->h = spa_buffer_find_meta_data(b->buf, SPA_META_Header, sizeof(*b->h));
|
||||
|
||||
if (d[0].data == NULL) {
|
||||
spa_log_error(this->log, "%p: need mapped memory", this);
|
||||
return -EINVAL;
|
||||
}
|
||||
spa_log_debug(this->log, "%p: %d %p data:%p", this, i, b->buf, d[0].data);
|
||||
}
|
||||
port->n_buffers = n_buffers;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
impl_node_port_set_io(void *object,
|
||||
enum spa_direction direction,
|
||||
uint32_t port_id,
|
||||
uint32_t id,
|
||||
void *data, size_t size)
|
||||
{
|
||||
struct state *this = object;
|
||||
struct port *port;
|
||||
|
||||
spa_return_val_if_fail(this != NULL, -EINVAL);
|
||||
|
||||
spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
|
||||
|
||||
port = GET_PORT(this, direction, port_id);
|
||||
|
||||
spa_log_debug(this->log, "%p: io %d %p %zd", this, id, data, size);
|
||||
|
||||
switch (id) {
|
||||
case SPA_IO_Buffers:
|
||||
port->io = data;
|
||||
break;
|
||||
case SPA_IO_RateMatch:
|
||||
port->rate_match = data;
|
||||
break;
|
||||
default:
|
||||
return -ENOENT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
static int impl_node_process(void *object)
|
||||
{
|
||||
struct state *this = object;
|
||||
struct port *port;
|
||||
struct spa_io_buffers *input;
|
||||
|
||||
spa_return_val_if_fail(this != NULL, -EINVAL);
|
||||
|
||||
port = GET_PORT(this, SPA_DIRECTION_INPUT, 0);
|
||||
|
||||
input = port->io;
|
||||
spa_return_val_if_fail(input != NULL, -EIO);
|
||||
|
||||
spa_log_trace_fp(this->log, "%p: process %d %d/%d", this, input->status,
|
||||
input->buffer_id, this->n_buffers);
|
||||
|
||||
if (this->position && this->position->clock.flags & SPA_IO_CLOCK_FLAG_FREEWHEEL) {
|
||||
input->status = SPA_STATUS_NEED_DATA;
|
||||
return SPA_STATUS_HAVE_DATA;
|
||||
}
|
||||
if (input->status == SPA_STATUS_HAVE_DATA &&
|
||||
input->buffer_id < port->n_buffers) {
|
||||
struct buffer *b = &port->buffers[input->buffer_id];
|
||||
|
||||
if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) {
|
||||
spa_log_warn(this->log, "%p: buffer %u in use",
|
||||
this, input->buffer_id);
|
||||
input->status = -EINVAL;
|
||||
return -EINVAL;
|
||||
}
|
||||
spa_log_trace_fp(this->log, "%p: queue buffer %u", this, input->buffer_id);
|
||||
spa_list_append(&port->ready, &b->link);
|
||||
SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT);
|
||||
input->buffer_id = SPA_ID_INVALID;
|
||||
|
||||
input->status = SPA_STATUS_OK;
|
||||
}
|
||||
return SPA_STATUS_HAVE_DATA;
|
||||
}
|
||||
|
||||
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_node_port_enum_params,
|
||||
.port_set_param = impl_node_port_set_param,
|
||||
.port_use_buffers = impl_node_port_use_buffers,
|
||||
.port_set_io = impl_node_port_set_io,
|
||||
.port_reuse_buffer = impl_node_port_reuse_buffer,
|
||||
.process = impl_node_process,
|
||||
};
|
||||
|
||||
static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface)
|
||||
{
|
||||
struct state *this;
|
||||
|
||||
spa_return_val_if_fail(handle != NULL, -EINVAL);
|
||||
spa_return_val_if_fail(interface != NULL, -EINVAL);
|
||||
|
||||
this = (struct state *) 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 state *this;
|
||||
spa_return_val_if_fail(handle != NULL, -EINVAL);
|
||||
this = (struct state *) handle;
|
||||
spa_avb_close(this);
|
||||
spa_avb_clear(this);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t
|
||||
impl_get_size(const struct spa_handle_factory *factory,
|
||||
const struct spa_dict *params)
|
||||
{
|
||||
return sizeof(struct state);
|
||||
}
|
||||
|
||||
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 state *this;
|
||||
struct port *port;
|
||||
|
||||
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 state *) handle;
|
||||
|
||||
this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log);
|
||||
avb_log_topic_init(this->log);
|
||||
|
||||
this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem);
|
||||
this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop);
|
||||
|
||||
if (this->data_loop == NULL) {
|
||||
spa_log_error(this->log, "a data loop is needed");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (this->data_system == NULL) {
|
||||
spa_log_error(this->log, "a data system is needed");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
this->node.iface = SPA_INTERFACE_INIT(
|
||||
SPA_TYPE_INTERFACE_Node,
|
||||
SPA_VERSION_NODE,
|
||||
&impl_node, this);
|
||||
|
||||
spa_hook_list_init(&this->hooks);
|
||||
|
||||
|
||||
this->info_all = SPA_NODE_CHANGE_MASK_FLAGS |
|
||||
SPA_NODE_CHANGE_MASK_PROPS |
|
||||
SPA_NODE_CHANGE_MASK_PARAMS;
|
||||
this->info = SPA_NODE_INFO_INIT();
|
||||
this->info.max_input_ports = 1;
|
||||
this->info.flags = SPA_NODE_FLAG_RT;
|
||||
this->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ);
|
||||
this->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE);
|
||||
this->params[NODE_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ);
|
||||
this->params[NODE_ProcessLatency] = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, SPA_PARAM_INFO_READWRITE);
|
||||
this->info.params = this->params;
|
||||
this->info.n_params = N_NODE_PARAMS;
|
||||
|
||||
reset_props(&this->props);
|
||||
|
||||
port = GET_PORT(this, SPA_DIRECTION_INPUT, 0);
|
||||
port->direction = SPA_DIRECTION_INPUT;
|
||||
|
||||
port->info_all = SPA_PORT_CHANGE_MASK_FLAGS |
|
||||
SPA_PORT_CHANGE_MASK_PARAMS;
|
||||
port->info = SPA_PORT_INFO_INIT();
|
||||
port->info.flags = SPA_PORT_FLAG_LIVE |
|
||||
SPA_PORT_FLAG_PHYSICAL |
|
||||
SPA_PORT_FLAG_TERMINAL;
|
||||
port->params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ);
|
||||
port->params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ);
|
||||
port->params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ);
|
||||
port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
|
||||
port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
|
||||
port->params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE);
|
||||
port->info.params = port->params;
|
||||
port->info.n_params = N_PORT_PARAMS;
|
||||
|
||||
spa_list_init(&port->ready);
|
||||
|
||||
this->latency[port->direction] = SPA_LATENCY_INFO(
|
||||
port->direction,
|
||||
.min_quantum = 1.0f,
|
||||
.max_quantum = 1.0f);
|
||||
this->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT);
|
||||
|
||||
return spa_avb_init(this, info);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static const struct spa_dict_item info_items[] = {
|
||||
{ SPA_KEY_FACTORY_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
|
||||
{ SPA_KEY_FACTORY_DESCRIPTION, "Play audio with AVB" },
|
||||
{ SPA_KEY_FACTORY_USAGE, "[]" },
|
||||
};
|
||||
|
||||
static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items);
|
||||
|
||||
const struct spa_handle_factory spa_avb_sink_factory = {
|
||||
SPA_VERSION_HANDLE_FACTORY,
|
||||
"avb.pcm.sink",
|
||||
&info,
|
||||
impl_get_size,
|
||||
impl_init,
|
||||
impl_enum_interface_info,
|
||||
};
|
||||
625
spa/plugins/avb/avb-pcm.c
Normal file
625
spa/plugins/avb/avb-pcm.c
Normal file
|
|
@ -0,0 +1,625 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sched.h>
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <sys/time.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <avtp.h>
|
||||
#include <avtp_aaf.h>
|
||||
|
||||
#include <spa/pod/filter.h>
|
||||
#include <spa/utils/string.h>
|
||||
#include <spa/support/system.h>
|
||||
#include <spa/utils/keys.h>
|
||||
|
||||
#include "avb-pcm.h"
|
||||
|
||||
static int avb_set_param(struct state *state, const char *k, const char *s)
|
||||
{
|
||||
int fmt_change = 0;
|
||||
if (spa_streq(k, SPA_KEY_AUDIO_CHANNELS)) {
|
||||
state->default_channels = atoi(s);
|
||||
fmt_change++;
|
||||
} else if (spa_streq(k, SPA_KEY_AUDIO_RATE)) {
|
||||
state->default_rate = atoi(s);
|
||||
fmt_change++;
|
||||
} else if (spa_streq(k, SPA_KEY_AUDIO_FORMAT)) {
|
||||
state->default_format = spa_avb_format_from_name(s, strlen(s));
|
||||
fmt_change++;
|
||||
} else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) {
|
||||
spa_avb_parse_position(&state->default_pos, s, strlen(s));
|
||||
fmt_change++;
|
||||
} else if (spa_streq(k, SPA_KEY_AUDIO_ALLOWED_RATES)) {
|
||||
state->n_allowed_rates = spa_avb_parse_rates(state->allowed_rates,
|
||||
MAX_RATES, s, strlen(s));
|
||||
fmt_change++;
|
||||
} else if (spa_streq(k, "latency.internal.rate")) {
|
||||
state->process_latency.rate = atoi(s);
|
||||
} else if (spa_streq(k, "latency.internal.ns")) {
|
||||
state->process_latency.ns = atoi(s);
|
||||
} else if (spa_streq(k, "clock.name")) {
|
||||
spa_scnprintf(state->clock_name,
|
||||
sizeof(state->clock_name), "%s", s);
|
||||
} else
|
||||
return 0;
|
||||
|
||||
if (fmt_change > 0) {
|
||||
struct port *port = &state->ports[0];
|
||||
port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
|
||||
port->params[PORT_EnumFormat].user++;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int position_to_string(struct channel_map *map, char *val, size_t len)
|
||||
{
|
||||
uint32_t i, o = 0;
|
||||
int r;
|
||||
o += snprintf(val, len, "[ ");
|
||||
for (i = 0; i < map->channels; i++) {
|
||||
r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ",
|
||||
spa_debug_type_find_short_name(spa_type_audio_channel,
|
||||
map->pos[i]));
|
||||
if (r < 0 || o + r >= len)
|
||||
return -ENOSPC;
|
||||
o += r;
|
||||
}
|
||||
if (len > o)
|
||||
o += snprintf(val+o, len-o, " ]");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uint32_array_to_string(uint32_t *vals, uint32_t n_vals, char *val, size_t len)
|
||||
{
|
||||
uint32_t i, o = 0;
|
||||
int r;
|
||||
o += snprintf(val, len, "[ ");
|
||||
for (i = 0; i < n_vals; i++) {
|
||||
r = snprintf(val+o, len-o, "%s%d", i == 0 ? "" : ", ", vals[i]);
|
||||
if (r < 0 || o + r >= len)
|
||||
return -ENOSPC;
|
||||
o += r;
|
||||
}
|
||||
if (len > o)
|
||||
o += snprintf(val+o, len-o, " ]");
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct spa_pod *spa_avb_enum_propinfo(struct state *state,
|
||||
uint32_t idx, struct spa_pod_builder *b)
|
||||
{
|
||||
struct spa_pod *param;
|
||||
|
||||
switch (idx) {
|
||||
case 0:
|
||||
param = spa_pod_builder_add_object(b,
|
||||
SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo,
|
||||
SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_CHANNELS),
|
||||
SPA_PROP_INFO_description, SPA_POD_String("Audio Channels"),
|
||||
SPA_PROP_INFO_type, SPA_POD_Int(state->default_channels),
|
||||
SPA_PROP_INFO_params, SPA_POD_Bool(true));
|
||||
break;
|
||||
case 1:
|
||||
param = spa_pod_builder_add_object(b,
|
||||
SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo,
|
||||
SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_RATE),
|
||||
SPA_PROP_INFO_description, SPA_POD_String("Audio Rate"),
|
||||
SPA_PROP_INFO_type, SPA_POD_Int(state->default_rate),
|
||||
SPA_PROP_INFO_params, SPA_POD_Bool(true));
|
||||
break;
|
||||
case 2:
|
||||
param = spa_pod_builder_add_object(b,
|
||||
SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo,
|
||||
SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_FORMAT),
|
||||
SPA_PROP_INFO_description, SPA_POD_String("Audio Format"),
|
||||
SPA_PROP_INFO_type, SPA_POD_String(
|
||||
spa_debug_type_find_short_name(spa_type_audio_format,
|
||||
state->default_format)),
|
||||
SPA_PROP_INFO_params, SPA_POD_Bool(true));
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
char buf[1024];
|
||||
position_to_string(&state->default_pos, buf, sizeof(buf));
|
||||
param = spa_pod_builder_add_object(b,
|
||||
SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo,
|
||||
SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_POSITION),
|
||||
SPA_PROP_INFO_description, SPA_POD_String("Audio Position"),
|
||||
SPA_PROP_INFO_type, SPA_POD_String(buf),
|
||||
SPA_PROP_INFO_params, SPA_POD_Bool(true));
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
char buf[1024];
|
||||
uint32_array_to_string(state->allowed_rates, state->n_allowed_rates, buf, sizeof(buf));
|
||||
param = spa_pod_builder_add_object(b,
|
||||
SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo,
|
||||
SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_ALLOWED_RATES),
|
||||
SPA_PROP_INFO_description, SPA_POD_String("Audio Allowed Rates"),
|
||||
SPA_PROP_INFO_type, SPA_POD_String(buf),
|
||||
SPA_PROP_INFO_params, SPA_POD_Bool(true));
|
||||
break;
|
||||
}
|
||||
case 13:
|
||||
param = spa_pod_builder_add_object(b,
|
||||
SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo,
|
||||
SPA_PROP_INFO_name, SPA_POD_String("latency.internal.rate"),
|
||||
SPA_PROP_INFO_description, SPA_POD_String("Internal latency in samples"),
|
||||
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(state->process_latency.rate,
|
||||
0, 65536),
|
||||
SPA_PROP_INFO_params, SPA_POD_Bool(true));
|
||||
break;
|
||||
case 14:
|
||||
param = spa_pod_builder_add_object(b,
|
||||
SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo,
|
||||
SPA_PROP_INFO_name, SPA_POD_String("latency.internal.ns"),
|
||||
SPA_PROP_INFO_description, SPA_POD_String("Internal latency in nanoseconds"),
|
||||
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Long(state->process_latency.ns,
|
||||
0, 2 * SPA_NSEC_PER_SEC),
|
||||
SPA_PROP_INFO_params, SPA_POD_Bool(true));
|
||||
break;
|
||||
case 15:
|
||||
param = spa_pod_builder_add_object(b,
|
||||
SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo,
|
||||
SPA_PROP_INFO_name, SPA_POD_String("clock.name"),
|
||||
SPA_PROP_INFO_description, SPA_POD_String("The name of the clock"),
|
||||
SPA_PROP_INFO_type, SPA_POD_String(state->clock_name),
|
||||
SPA_PROP_INFO_params, SPA_POD_Bool(true));
|
||||
break;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
return param;
|
||||
}
|
||||
|
||||
int spa_avb_add_prop_params(struct state *state, struct spa_pod_builder *b)
|
||||
{
|
||||
struct spa_pod_frame f[1];
|
||||
char buf[1024];
|
||||
|
||||
spa_pod_builder_prop(b, SPA_PROP_params, 0);
|
||||
spa_pod_builder_push_struct(b, &f[0]);
|
||||
|
||||
spa_pod_builder_string(b, SPA_KEY_AUDIO_CHANNELS);
|
||||
spa_pod_builder_int(b, state->default_channels);
|
||||
|
||||
spa_pod_builder_string(b, SPA_KEY_AUDIO_RATE);
|
||||
spa_pod_builder_int(b, state->default_rate);
|
||||
|
||||
spa_pod_builder_string(b, SPA_KEY_AUDIO_FORMAT);
|
||||
spa_pod_builder_string(b,
|
||||
spa_debug_type_find_short_name(spa_type_audio_format,
|
||||
state->default_format));
|
||||
|
||||
position_to_string(&state->default_pos, buf, sizeof(buf));
|
||||
spa_pod_builder_string(b, SPA_KEY_AUDIO_POSITION);
|
||||
spa_pod_builder_string(b, buf);
|
||||
|
||||
uint32_array_to_string(state->allowed_rates, state->n_allowed_rates,
|
||||
buf, sizeof(buf));
|
||||
spa_pod_builder_string(b, SPA_KEY_AUDIO_ALLOWED_RATES);
|
||||
spa_pod_builder_string(b, buf);
|
||||
|
||||
spa_pod_builder_string(b, "latency.internal.rate");
|
||||
spa_pod_builder_int(b, state->process_latency.rate);
|
||||
|
||||
spa_pod_builder_string(b, "latency.internal.ns");
|
||||
spa_pod_builder_long(b, state->process_latency.ns);
|
||||
|
||||
spa_pod_builder_string(b, "clock.name");
|
||||
spa_pod_builder_string(b, state->clock_name);
|
||||
|
||||
spa_pod_builder_pop(b, &f[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spa_avb_parse_prop_params(struct state *state, struct spa_pod *params)
|
||||
{
|
||||
struct spa_pod_parser prs;
|
||||
struct spa_pod_frame f;
|
||||
int changed = 0;
|
||||
|
||||
if (params == NULL)
|
||||
return 0;
|
||||
|
||||
spa_pod_parser_pod(&prs, params);
|
||||
if (spa_pod_parser_push_struct(&prs, &f) < 0)
|
||||
return 0;
|
||||
|
||||
while (true) {
|
||||
const char *name;
|
||||
struct spa_pod *pod;
|
||||
char value[512];
|
||||
|
||||
if (spa_pod_parser_get_string(&prs, &name) < 0)
|
||||
break;
|
||||
|
||||
if (spa_pod_parser_get_pod(&prs, &pod) < 0)
|
||||
break;
|
||||
if (spa_pod_is_string(pod)) {
|
||||
spa_pod_copy_string(pod, sizeof(value), value);
|
||||
} else if (spa_pod_is_int(pod)) {
|
||||
snprintf(value, sizeof(value), "%d",
|
||||
SPA_POD_VALUE(struct spa_pod_int, pod));
|
||||
} else if (spa_pod_is_long(pod)) {
|
||||
snprintf(value, sizeof(value), "%"PRIi64,
|
||||
SPA_POD_VALUE(struct spa_pod_long, pod));
|
||||
} else if (spa_pod_is_bool(pod)) {
|
||||
snprintf(value, sizeof(value), "%s",
|
||||
SPA_POD_VALUE(struct spa_pod_bool, pod) ?
|
||||
"true" : "false");
|
||||
} else
|
||||
continue;
|
||||
|
||||
spa_log_info(state->log, "key:'%s' val:'%s'", name, value);
|
||||
avb_set_param(state, name, value);
|
||||
changed++;
|
||||
}
|
||||
if (changed > 0) {
|
||||
state->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS;
|
||||
state->params[NODE_Props].user++;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
int spa_avb_init(struct state *state, const struct spa_dict *info)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
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, "clock.quantum-limit")) {
|
||||
spa_atou32(s, &state->quantum_limit, 0);
|
||||
} else {
|
||||
avb_set_param(state, k, s);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spa_avb_clear(struct state *state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spa_avb_open(struct state *state, const char *params)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (state->opened)
|
||||
return 0;
|
||||
|
||||
if ((err = spa_system_timerfd_create(state->data_system,
|
||||
CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0)
|
||||
goto error_exit_close;
|
||||
|
||||
state->timerfd = err;
|
||||
|
||||
state->opened = true;
|
||||
|
||||
return 0;
|
||||
|
||||
error_exit_close:
|
||||
return err;
|
||||
}
|
||||
|
||||
int spa_avb_close(struct state *state)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
if (!state->opened)
|
||||
return 0;
|
||||
|
||||
spa_system_close(state->data_system, state->timerfd);
|
||||
|
||||
state->opened = false;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
struct format_info {
|
||||
uint32_t spa_format;
|
||||
int aaf_format;
|
||||
};
|
||||
|
||||
static const struct format_info format_info[] = {
|
||||
{ SPA_AUDIO_FORMAT_UNKNOWN, AVTP_AAF_FORMAT_USER},
|
||||
{ SPA_AUDIO_FORMAT_F32_BE, AVTP_AAF_FORMAT_FLOAT_32BIT },
|
||||
{ SPA_AUDIO_FORMAT_S32_BE, AVTP_AAF_FORMAT_INT_32BIT },
|
||||
{ SPA_AUDIO_FORMAT_S24_BE, AVTP_AAF_FORMAT_INT_24BIT },
|
||||
{ SPA_AUDIO_FORMAT_S16_BE, AVTP_AAF_FORMAT_INT_16BIT },
|
||||
};
|
||||
|
||||
static int spa_format_to_avb(uint32_t format)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) {
|
||||
if (format_info[i].spa_format == format)
|
||||
return format_info[i].aaf_format;
|
||||
}
|
||||
return AVTP_AAF_FORMAT_USER;
|
||||
}
|
||||
|
||||
struct rate_info {
|
||||
uint32_t spa_rate;
|
||||
int aaf_rate;
|
||||
};
|
||||
|
||||
static const struct rate_info rate_info[] = {
|
||||
{ 0, AVTP_AAF_PCM_NSR_USER },
|
||||
{ 8000, AVTP_AAF_PCM_NSR_8KHZ },
|
||||
{ 16000, AVTP_AAF_PCM_NSR_16KHZ },
|
||||
{ 24000, AVTP_AAF_PCM_NSR_24KHZ },
|
||||
{ 32000, AVTP_AAF_PCM_NSR_32KHZ },
|
||||
{ 44100, AVTP_AAF_PCM_NSR_44_1KHZ },
|
||||
{ 48000, AVTP_AAF_PCM_NSR_48KHZ },
|
||||
{ 88200, AVTP_AAF_PCM_NSR_88_2KHZ },
|
||||
{ 96000, AVTP_AAF_PCM_NSR_96KHZ },
|
||||
{ 176400, AVTP_AAF_PCM_NSR_176_4KHZ },
|
||||
{ 192000, AVTP_AAF_PCM_NSR_192KHZ },
|
||||
};
|
||||
|
||||
static int spa_rate_to_avb(uint32_t rate)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < SPA_N_ELEMENTS(rate_info); i++) {
|
||||
if (rate_info[i].spa_rate == rate)
|
||||
return rate_info[i].aaf_rate;
|
||||
}
|
||||
return AVTP_AAF_PCM_NSR_USER;
|
||||
}
|
||||
|
||||
int
|
||||
spa_avb_enum_format(struct state *state, int seq, uint32_t start, uint32_t num,
|
||||
const struct spa_pod *filter)
|
||||
{
|
||||
uint8_t buffer[4096];
|
||||
struct spa_pod_builder b = { 0 };
|
||||
struct spa_pod_frame f[2];
|
||||
struct spa_pod *fmt;
|
||||
int res = 0;
|
||||
struct spa_result_node_params result;
|
||||
uint32_t count = 0;
|
||||
|
||||
result.id = SPA_PARAM_EnumFormat;
|
||||
result.next = start;
|
||||
|
||||
next:
|
||||
result.index = result.next++;
|
||||
|
||||
if (result.index > 0)
|
||||
return 0;
|
||||
|
||||
spa_pod_builder_init(&b, buffer, sizeof(buffer));
|
||||
|
||||
spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
|
||||
spa_pod_builder_add(&b,
|
||||
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
|
||||
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
|
||||
0);
|
||||
|
||||
spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_format, 0);
|
||||
if (state->default_format != 0) {
|
||||
spa_pod_builder_id(&b, state->default_format);
|
||||
} else {
|
||||
spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_Enum, 0);
|
||||
spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_F32_BE);
|
||||
spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_F32_BE);
|
||||
spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_S32_BE);
|
||||
spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_S24_BE);
|
||||
spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_S16_BE);
|
||||
spa_pod_builder_pop(&b, &f[1]);
|
||||
}
|
||||
spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_rate, 0);
|
||||
if (state->default_rate != 0) {
|
||||
spa_pod_builder_int(&b, state->default_rate);
|
||||
} else {
|
||||
spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_Enum, 0);
|
||||
spa_pod_builder_int(&b, 48000);
|
||||
spa_pod_builder_int(&b, 8000);
|
||||
spa_pod_builder_int(&b, 16000);
|
||||
spa_pod_builder_int(&b, 24000);
|
||||
spa_pod_builder_int(&b, 32000);
|
||||
spa_pod_builder_int(&b, 44100);
|
||||
spa_pod_builder_int(&b, 48000);
|
||||
spa_pod_builder_int(&b, 88200);
|
||||
spa_pod_builder_int(&b, 96000);
|
||||
spa_pod_builder_int(&b, 176400);
|
||||
spa_pod_builder_int(&b, 192000);
|
||||
spa_pod_builder_pop(&b, &f[1]);
|
||||
}
|
||||
spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_channels, 0);
|
||||
if (state->default_channels != 0) {
|
||||
spa_pod_builder_int(&b, state->default_channels);
|
||||
} else {
|
||||
spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_Range, 0);
|
||||
spa_pod_builder_int(&b, 8);
|
||||
spa_pod_builder_int(&b, 2);
|
||||
spa_pod_builder_int(&b, 32);
|
||||
spa_pod_builder_pop(&b, &f[1]);
|
||||
}
|
||||
fmt = spa_pod_builder_pop(&b, &f[0]);
|
||||
|
||||
if (spa_pod_filter(&b, &result.param, fmt, filter) < 0)
|
||||
goto next;
|
||||
|
||||
spa_node_emit_result(&state->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
|
||||
|
||||
if (++count != num)
|
||||
goto next;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int spa_avb_set_format(struct state *state, struct spa_audio_info *fmt, uint32_t flags)
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void spa_avb_recycle_buffer(struct state *this, struct port *port, uint32_t buffer_id)
|
||||
{
|
||||
struct buffer *b = &port->buffers[buffer_id];
|
||||
|
||||
if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) {
|
||||
spa_log_trace_fp(this->log, "%p: recycle buffer %u", this, buffer_id);
|
||||
spa_list_append(&port->free, &b->link);
|
||||
SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT);
|
||||
}
|
||||
}
|
||||
|
||||
static void reset_buffers(struct state *this, struct port *port)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
spa_list_init(&port->free);
|
||||
spa_list_init(&port->ready);
|
||||
|
||||
for (i = 0; i < port->n_buffers; i++) {
|
||||
struct buffer *b = &port->buffers[i];
|
||||
if (port->direction == SPA_DIRECTION_INPUT) {
|
||||
SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT);
|
||||
spa_node_call_reuse_buffer(&this->callbacks, 0, b->id);
|
||||
} else {
|
||||
spa_list_append(&port->free, &b->link);
|
||||
SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int set_timers(struct state *state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void avb_on_socket_event(struct spa_source *source)
|
||||
{
|
||||
}
|
||||
|
||||
static void avb_on_timeout_event(struct spa_source *source)
|
||||
{
|
||||
}
|
||||
|
||||
int spa_avb_start(struct state *state)
|
||||
{
|
||||
if (state->started)
|
||||
return 0;
|
||||
|
||||
if (state->position) {
|
||||
state->duration = state->position->clock.duration;
|
||||
state->rate_denom = state->position->clock.rate.denom;
|
||||
} else {
|
||||
state->duration = 1024;
|
||||
state->rate_denom = state->rate;
|
||||
}
|
||||
|
||||
spa_dll_init(&state->dll);
|
||||
state->max_error = (256.0 * state->rate) / state->rate_denom;
|
||||
|
||||
state->timer_source.func = avb_on_timeout_event;
|
||||
state->timer_source.data = state;
|
||||
state->timer_source.fd = state->timerfd;
|
||||
state->timer_source.mask = SPA_IO_IN;
|
||||
state->timer_source.rmask = 0;
|
||||
spa_loop_add_source(state->data_loop, &state->timer_source);
|
||||
|
||||
if (state->ports[0].direction == SPA_DIRECTION_OUTPUT) {
|
||||
state->sock_source.func = avb_on_socket_event;
|
||||
state->sock_source.data = state;
|
||||
state->sock_source.fd = state->sockfd;
|
||||
state->sock_source.mask = SPA_IO_IN;
|
||||
state->sock_source.rmask = 0;
|
||||
spa_loop_add_source(state->data_loop, &state->sock_source);
|
||||
}
|
||||
|
||||
reset_buffers(state, &state->ports[0]);
|
||||
|
||||
set_timers(state);
|
||||
|
||||
state->started = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_reassign_follower(struct spa_loop *loop,
|
||||
bool async,
|
||||
uint32_t seq,
|
||||
const void *data,
|
||||
size_t size,
|
||||
void *user_data)
|
||||
{
|
||||
struct state *state = user_data;
|
||||
set_timers(state);
|
||||
spa_dll_init(&state->dll);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spa_avb_reassign_follower(struct state *state)
|
||||
{
|
||||
bool following, freewheel;
|
||||
|
||||
if (!state->started)
|
||||
return 0;
|
||||
|
||||
if (following != state->following) {
|
||||
spa_log_debug(state->log, "%p: reassign follower %d->%d", state, state->following, following);
|
||||
state->following = following;
|
||||
spa_loop_invoke(state->data_loop, do_reassign_follower, 0, NULL, 0, true, state);
|
||||
}
|
||||
|
||||
freewheel = state->position &&
|
||||
SPA_FLAG_IS_SET(state->position->clock.flags, SPA_IO_CLOCK_FLAG_FREEWHEEL);
|
||||
|
||||
if (state->freewheel != freewheel) {
|
||||
spa_log_debug(state->log, "%p: freewheel %d->%d", state, state->freewheel, freewheel);
|
||||
state->freewheel = freewheel;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_remove_source(struct spa_loop *loop,
|
||||
bool async,
|
||||
uint32_t seq,
|
||||
const void *data,
|
||||
size_t size,
|
||||
void *user_data)
|
||||
{
|
||||
struct state *state = user_data;
|
||||
struct itimerspec ts;
|
||||
|
||||
spa_loop_remove_source(state->data_loop, &state->timer_source);
|
||||
ts.it_value.tv_sec = 0;
|
||||
ts.it_value.tv_nsec = 0;
|
||||
ts.it_interval.tv_sec = 0;
|
||||
ts.it_interval.tv_nsec = 0;
|
||||
spa_system_timerfd_settime(state->data_system, state->timerfd, 0, &ts, NULL);
|
||||
|
||||
if (state->ports[0].direction == SPA_DIRECTION_OUTPUT) {
|
||||
spa_loop_remove_source(state->data_loop, &state->sock_source);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spa_avb_pause(struct state *state)
|
||||
{
|
||||
if (!state->started)
|
||||
return 0;
|
||||
|
||||
spa_log_debug(state->log, "%p: pause", state);
|
||||
|
||||
spa_loop_invoke(state->data_loop, do_remove_source, 0, NULL, 0, true, state);
|
||||
|
||||
state->started = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
266
spa/plugins/avb/avb-pcm.h
Normal file
266
spa/plugins/avb/avb-pcm.h
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
/* Spa AVB PCM
|
||||
*
|
||||
* Copyright © 2022 Wim Taymans
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef SPA_AVB_PCM_H
|
||||
#define SPA_AVB_PCM_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <math.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/if_packet.h>
|
||||
#include <linux/net_tstamp.h>
|
||||
#include <limits.h>
|
||||
#include <net/if.h>
|
||||
|
||||
#include <spa/support/plugin.h>
|
||||
#include <spa/support/loop.h>
|
||||
#include <spa/utils/list.h>
|
||||
#include <spa/utils/json.h>
|
||||
|
||||
#include <spa/node/node.h>
|
||||
#include <spa/node/utils.h>
|
||||
#include <spa/node/io.h>
|
||||
#include <spa/debug/types.h>
|
||||
#include <spa/param/param.h>
|
||||
#include <spa/param/latency-utils.h>
|
||||
#include <spa/param/audio/format-utils.h>
|
||||
|
||||
#include "avb.h"
|
||||
#include "dll.h"
|
||||
|
||||
#define MAX_RATES 16
|
||||
|
||||
#define DEFAULT_PERIOD 1024u
|
||||
#define DEFAULT_RATE 48000u
|
||||
#define DEFAULT_CHANNELS 8u
|
||||
|
||||
struct props {
|
||||
char ifname[IFNAMSIZ];
|
||||
unsigned char addr[ETH_ALEN];
|
||||
int prio;
|
||||
uint64_t streamid;
|
||||
int mtt;
|
||||
int t_uncertainty;
|
||||
uint32_t frames_per_pdu;
|
||||
int ptime_tolerance;
|
||||
};
|
||||
|
||||
#define MAX_BUFFERS 32
|
||||
|
||||
struct buffer {
|
||||
uint32_t id;
|
||||
#define BUFFER_FLAG_OUT (1<<0)
|
||||
uint32_t flags;
|
||||
struct spa_buffer *buf;
|
||||
struct spa_meta_header *h;
|
||||
struct spa_list link;
|
||||
};
|
||||
|
||||
#define BW_MAX 0.128
|
||||
#define BW_MED 0.064
|
||||
#define BW_MIN 0.016
|
||||
#define BW_PERIOD (3 * SPA_NSEC_PER_SEC)
|
||||
|
||||
struct channel_map {
|
||||
uint32_t channels;
|
||||
uint32_t pos[SPA_AUDIO_MAX_CHANNELS];
|
||||
};
|
||||
|
||||
struct port {
|
||||
enum spa_direction direction;
|
||||
uint32_t id;
|
||||
|
||||
uint64_t info_all;
|
||||
struct spa_port_info info;
|
||||
#define PORT_EnumFormat 0
|
||||
#define PORT_Meta 1
|
||||
#define PORT_IO 2
|
||||
#define PORT_Format 3
|
||||
#define PORT_Buffers 4
|
||||
#define PORT_Latency 5
|
||||
#define N_PORT_PARAMS 6
|
||||
struct spa_param_info params[N_PORT_PARAMS];
|
||||
|
||||
bool have_format;
|
||||
struct spa_audio_info current_format;
|
||||
|
||||
struct spa_io_buffers *io;
|
||||
struct spa_io_rate_match *rate_match;
|
||||
struct buffer buffers[MAX_BUFFERS];
|
||||
unsigned int n_buffers;
|
||||
|
||||
struct spa_list free;
|
||||
struct spa_list ready;
|
||||
};
|
||||
|
||||
struct state {
|
||||
struct spa_handle handle;
|
||||
struct spa_node node;
|
||||
|
||||
struct spa_log *log;
|
||||
struct spa_system *data_system;
|
||||
struct spa_loop *data_loop;
|
||||
|
||||
struct spa_hook_list hooks;
|
||||
struct spa_callbacks callbacks;
|
||||
|
||||
uint64_t info_all;
|
||||
struct spa_node_info info;
|
||||
#define NODE_PropInfo 0
|
||||
#define NODE_Props 1
|
||||
#define NODE_IO 2
|
||||
#define NODE_ProcessLatency 3
|
||||
#define N_NODE_PARAMS 4
|
||||
struct spa_param_info params[N_NODE_PARAMS];
|
||||
struct props props;
|
||||
|
||||
uint32_t default_period_size;
|
||||
uint32_t default_format;
|
||||
unsigned int default_channels;
|
||||
unsigned int default_rate;
|
||||
uint32_t allowed_rates[MAX_RATES];
|
||||
uint32_t n_allowed_rates;
|
||||
struct channel_map default_pos;
|
||||
char clock_name[64];
|
||||
uint32_t quantum_limit;
|
||||
|
||||
int rate;
|
||||
int channels;
|
||||
size_t frame_size;
|
||||
int blocks;
|
||||
uint32_t rate_denom;
|
||||
|
||||
struct spa_io_clock *clock;
|
||||
struct spa_io_position *position;
|
||||
|
||||
struct port ports[1];
|
||||
|
||||
uint32_t duration;
|
||||
uint32_t threshold;
|
||||
unsigned int following:1;
|
||||
unsigned int matching:1;
|
||||
unsigned int resample:1;
|
||||
unsigned int opened:1;
|
||||
unsigned int started:1;
|
||||
unsigned int freewheel:1;
|
||||
|
||||
int timerfd;
|
||||
struct spa_source timer_source;
|
||||
int sockfd;
|
||||
struct spa_source sock_source;
|
||||
|
||||
struct spa_dll dll;
|
||||
double max_error;
|
||||
|
||||
struct spa_latency_info latency[2];
|
||||
struct spa_process_latency_info process_latency;
|
||||
};
|
||||
|
||||
struct spa_pod *spa_avb_enum_propinfo(struct state *state,
|
||||
uint32_t idx, struct spa_pod_builder *b);
|
||||
int spa_avb_add_prop_params(struct state *state, struct spa_pod_builder *b);
|
||||
int spa_avb_parse_prop_params(struct state *state, struct spa_pod *params);
|
||||
|
||||
int spa_avb_enum_format(struct state *state, int seq,
|
||||
uint32_t start, uint32_t num,
|
||||
const struct spa_pod *filter);
|
||||
|
||||
int spa_avb_set_format(struct state *state, struct spa_audio_info *info, uint32_t flags);
|
||||
|
||||
int spa_avb_init(struct state *state, const struct spa_dict *info);
|
||||
int spa_avb_clear(struct state *state);
|
||||
|
||||
int spa_avb_open(struct state *state, const char *params);
|
||||
int spa_avb_start(struct state *state);
|
||||
int spa_avb_reassign_follower(struct state *state);
|
||||
int spa_avb_pause(struct state *state);
|
||||
int spa_avb_close(struct state *state);
|
||||
|
||||
int spa_avb_write(struct state *state);
|
||||
int spa_avb_read(struct state *state);
|
||||
int spa_avb_skip(struct state *state);
|
||||
|
||||
void spa_avb_recycle_buffer(struct state *state, struct port *port, uint32_t buffer_id);
|
||||
|
||||
static inline uint32_t spa_avb_format_from_name(const char *name, size_t len)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; spa_type_audio_format[i].name; i++) {
|
||||
if (strncmp(name, spa_debug_type_short_name(spa_type_audio_format[i].name), len) == 0)
|
||||
return spa_type_audio_format[i].type;
|
||||
}
|
||||
return SPA_AUDIO_FORMAT_UNKNOWN;
|
||||
}
|
||||
|
||||
static inline uint32_t spa_avb_channel_from_name(const char *name)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; spa_type_audio_channel[i].name; i++) {
|
||||
if (strcmp(name, spa_debug_type_short_name(spa_type_audio_channel[i].name)) == 0)
|
||||
return spa_type_audio_channel[i].type;
|
||||
}
|
||||
return SPA_AUDIO_CHANNEL_UNKNOWN;
|
||||
}
|
||||
|
||||
static inline void spa_avb_parse_position(struct channel_map *map, const char *val, size_t len)
|
||||
{
|
||||
struct spa_json it[2];
|
||||
char v[256];
|
||||
|
||||
spa_json_init(&it[0], val, len);
|
||||
if (spa_json_enter_array(&it[0], &it[1]) <= 0)
|
||||
spa_json_init(&it[1], val, len);
|
||||
|
||||
map->channels = 0;
|
||||
while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 &&
|
||||
map->channels < SPA_AUDIO_MAX_CHANNELS) {
|
||||
map->pos[map->channels++] = spa_avb_channel_from_name(v);
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint32_t spa_avb_parse_rates(uint32_t *rates, uint32_t max, const char *val, size_t len)
|
||||
{
|
||||
struct spa_json it[2];
|
||||
char v[256];
|
||||
uint32_t count;
|
||||
|
||||
spa_json_init(&it[0], val, len);
|
||||
if (spa_json_enter_array(&it[0], &it[1]) <= 0)
|
||||
spa_json_init(&it[1], val, len);
|
||||
|
||||
count = 0;
|
||||
while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && count < max)
|
||||
rates[count++] = atoi(v);
|
||||
return count;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* SPA_AVB_PCM_H */
|
||||
50
spa/plugins/avb/avb.c
Normal file
50
spa/plugins/avb/avb.c
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/* Spa AVB support
|
||||
*
|
||||
* Copyright © 2022 Wim Taymans
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <spa/support/plugin.h>
|
||||
#include <spa/support/log.h>
|
||||
|
||||
extern const struct spa_handle_factory spa_avb_sink_factory;
|
||||
|
||||
struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.avb");
|
||||
struct spa_log_topic *avb_log_topic = &log_topic;
|
||||
|
||||
SPA_EXPORT
|
||||
int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index)
|
||||
{
|
||||
spa_return_val_if_fail(factory != NULL, -EINVAL);
|
||||
spa_return_val_if_fail(index != NULL, -EINVAL);
|
||||
|
||||
switch (*index) {
|
||||
case 0:
|
||||
*factory = &spa_avb_sink_factory;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
(*index)++;
|
||||
return 1;
|
||||
}
|
||||
39
spa/plugins/avb/avb.h
Normal file
39
spa/plugins/avb/avb.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/* Spa AVB
|
||||
*
|
||||
* Copyright © 2022 Wim Taymans
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef SPA_AVB_H
|
||||
#define SPA_AVB_H
|
||||
|
||||
#include <spa/support/log.h>
|
||||
|
||||
#undef SPA_LOG_TOPIC_DEFAULT
|
||||
#define SPA_LOG_TOPIC_DEFAULT avb_log_topic
|
||||
extern struct spa_log_topic *avb_log_topic;
|
||||
|
||||
static inline void avb_log_topic_init(struct spa_log *log)
|
||||
{
|
||||
spa_log_topic_init(log, avb_log_topic);
|
||||
}
|
||||
|
||||
#endif /* SPA_AVB_H */
|
||||
71
spa/plugins/avb/dll.h
Normal file
71
spa/plugins/avb/dll.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/* Simple DLL
|
||||
*
|
||||
* Copyright © 2019 Wim Taymans
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice (including the next
|
||||
* paragraph) shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef SPA_DLL_H
|
||||
#define SPA_DLL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <math.h>
|
||||
|
||||
#define SPA_DLL_BW_MAX 0.128
|
||||
#define SPA_DLL_BW_MIN 0.016
|
||||
|
||||
struct spa_dll {
|
||||
double bw;
|
||||
double z1, z2, z3;
|
||||
double w0, w1, w2;
|
||||
};
|
||||
|
||||
static inline void spa_dll_init(struct spa_dll *dll)
|
||||
{
|
||||
dll->bw = 0.0;
|
||||
dll->z1 = dll->z2 = dll->z3 = 0.0;
|
||||
}
|
||||
|
||||
static inline void spa_dll_set_bw(struct spa_dll *dll, double bw, uint32_t period, uint32_t rate)
|
||||
{
|
||||
double w = 2 * M_PI * bw * period / rate;
|
||||
dll->w0 = 1.0 - exp (-20.0 * w);
|
||||
dll->w1 = w * 1.5 / period;
|
||||
dll->w2 = w / 1.5;
|
||||
dll->bw = bw;
|
||||
}
|
||||
|
||||
static inline double spa_dll_update(struct spa_dll *dll, double err)
|
||||
{
|
||||
dll->z1 += dll->w0 * (dll->w1 * err - dll->z1);
|
||||
dll->z2 += dll->w0 * (dll->z1 - dll->z2);
|
||||
dll->z3 += dll->w2 * dll->z2;
|
||||
return 1.0 - (dll->z2 + dll->z3);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* SPA_DLL_H */
|
||||
14
spa/plugins/avb/meson.build
Normal file
14
spa/plugins/avb/meson.build
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
spa_avb_sources = ['avb.c',
|
||||
'avb.h',
|
||||
'avb-pcm-sink.c',
|
||||
'avb-pcm.c' ]
|
||||
|
||||
spa_avb = shared_library(
|
||||
'spa-avb',
|
||||
[ spa_avb_sources ],
|
||||
c_args : acp_c_args,
|
||||
include_directories : [configinc],
|
||||
dependencies : [ spa_dep, mathlib, epoll_shim_dep ],
|
||||
install : true,
|
||||
install_dir : spa_plugindir / 'avb'
|
||||
)
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
if alsa_dep.found()
|
||||
subdir('alsa')
|
||||
endif
|
||||
if get_option('avb').allowed()
|
||||
subdir('avb')
|
||||
endif
|
||||
if get_option('audioconvert').allowed()
|
||||
subdir('audioconvert')
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ context.spa-libs = {
|
|||
# that factory.
|
||||
#
|
||||
audio.convert.* = audioconvert/libspa-audioconvert
|
||||
avb.* = avb/libspa-avb
|
||||
api.alsa.* = alsa/libspa-alsa
|
||||
api.v4l2.* = v4l2/libspa-v4l2
|
||||
api.libcamera.* = libcamera/libspa-libcamera
|
||||
|
|
@ -235,6 +236,15 @@ context.objects = [
|
|||
# audio.position = "FL,FR"
|
||||
# }
|
||||
#}
|
||||
{ factory = adapter
|
||||
args = {
|
||||
factory.name = avb.pcm.sink
|
||||
node.name = AVB-sink
|
||||
node.description = "AVB Sink"
|
||||
media.class = "Audio/Sink"
|
||||
audio.channels = 8
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
context.exec = [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue