2018-06-01 11:28:31 +02:00
|
|
|
/* PipeWire
|
|
|
|
|
* Copyright (C) 2018 Wim Taymans <wim.taymans@gmail.com>
|
|
|
|
|
*
|
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
|
* modify it under the terms of the GNU Library General Public
|
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
|
* version 2 of the License, or (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
|
* Library General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU Library General Public
|
|
|
|
|
* License along with this library; if not, write to the
|
|
|
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
|
|
|
* Boston, MA 02110-1301, USA.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <errno.h>
|
2018-06-22 17:41:12 +02:00
|
|
|
#include <time.h>
|
2018-06-01 11:28:31 +02:00
|
|
|
|
|
|
|
|
#include <spa/utils/defs.h>
|
2019-08-13 18:46:27 +02:00
|
|
|
#include <spa/param/props.h>
|
2018-06-01 11:28:31 +02:00
|
|
|
|
|
|
|
|
#include <pulse/stream.h>
|
2018-06-05 20:10:31 +02:00
|
|
|
#include <pulse/timeval.h>
|
2018-06-26 16:57:49 +02:00
|
|
|
#include <pulse/xmalloc.h>
|
2018-06-01 11:28:31 +02:00
|
|
|
|
|
|
|
|
#include <pipewire/stream.h>
|
2019-05-24 15:47:05 +02:00
|
|
|
#include <pipewire/keys.h>
|
2018-10-23 16:50:17 +02:00
|
|
|
#include "core-format.h"
|
2018-06-01 11:28:31 +02:00
|
|
|
#include "internal.h"
|
|
|
|
|
|
2018-07-03 22:03:25 +02:00
|
|
|
#define MIN_QUEUED 1
|
|
|
|
|
|
2018-07-19 13:35:46 +02:00
|
|
|
#define MAX_SIZE (4*1024*1024)
|
2018-06-26 16:57:49 +02:00
|
|
|
|
2018-06-22 17:41:12 +02:00
|
|
|
static const uint32_t audio_formats[] = {
|
2018-08-24 11:00:33 +02:00
|
|
|
[PA_SAMPLE_U8] = SPA_AUDIO_FORMAT_U8,
|
|
|
|
|
[PA_SAMPLE_ALAW] = SPA_AUDIO_FORMAT_UNKNOWN,
|
|
|
|
|
[PA_SAMPLE_ULAW] = SPA_AUDIO_FORMAT_UNKNOWN,
|
|
|
|
|
[PA_SAMPLE_S16NE] = SPA_AUDIO_FORMAT_S16,
|
|
|
|
|
[PA_SAMPLE_S16RE] = SPA_AUDIO_FORMAT_S16_OE,
|
|
|
|
|
[PA_SAMPLE_FLOAT32NE] = SPA_AUDIO_FORMAT_F32,
|
|
|
|
|
[PA_SAMPLE_FLOAT32RE] = SPA_AUDIO_FORMAT_F32_OE,
|
|
|
|
|
[PA_SAMPLE_S32NE] = SPA_AUDIO_FORMAT_S32,
|
|
|
|
|
[PA_SAMPLE_S32RE] = SPA_AUDIO_FORMAT_S32_OE,
|
|
|
|
|
[PA_SAMPLE_S24NE] = SPA_AUDIO_FORMAT_S24,
|
|
|
|
|
[PA_SAMPLE_S24RE] = SPA_AUDIO_FORMAT_S24_OE,
|
|
|
|
|
[PA_SAMPLE_S24_32NE] = SPA_AUDIO_FORMAT_S24_32,
|
|
|
|
|
[PA_SAMPLE_S24_32RE] = SPA_AUDIO_FORMAT_S24_32_OE,
|
2018-06-22 17:41:12 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static inline uint32_t format_pa2id(pa_stream *s, pa_sample_format_t format)
|
|
|
|
|
{
|
2019-01-07 15:04:34 +01:00
|
|
|
if (format < 0 || (size_t)format >= SPA_N_ELEMENTS(audio_formats))
|
2018-08-24 11:00:33 +02:00
|
|
|
return SPA_AUDIO_FORMAT_UNKNOWN;
|
|
|
|
|
return audio_formats[format];
|
2018-06-22 17:41:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline pa_sample_format_t format_id2pa(pa_stream *s, uint32_t id)
|
|
|
|
|
{
|
2019-01-07 15:04:34 +01:00
|
|
|
size_t i;
|
2018-06-22 17:41:12 +02:00
|
|
|
for (i = 0; i < SPA_N_ELEMENTS(audio_formats); i++) {
|
2018-08-24 11:00:33 +02:00
|
|
|
if (id == audio_formats[i])
|
2018-06-22 17:41:12 +02:00
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
return PA_SAMPLE_INVALID;
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-17 09:51:39 +02:00
|
|
|
static const uint32_t audio_channels[] = {
|
|
|
|
|
[PA_CHANNEL_POSITION_MONO] = SPA_AUDIO_CHANNEL_MONO,
|
|
|
|
|
|
|
|
|
|
[PA_CHANNEL_POSITION_FRONT_LEFT] = SPA_AUDIO_CHANNEL_FL,
|
|
|
|
|
[PA_CHANNEL_POSITION_FRONT_RIGHT] = SPA_AUDIO_CHANNEL_FR,
|
|
|
|
|
[PA_CHANNEL_POSITION_FRONT_CENTER] = SPA_AUDIO_CHANNEL_FC,
|
|
|
|
|
|
|
|
|
|
[PA_CHANNEL_POSITION_REAR_CENTER] = SPA_AUDIO_CHANNEL_RC,
|
|
|
|
|
[PA_CHANNEL_POSITION_REAR_LEFT] = SPA_AUDIO_CHANNEL_RL,
|
|
|
|
|
[PA_CHANNEL_POSITION_REAR_RIGHT] = SPA_AUDIO_CHANNEL_RR,
|
|
|
|
|
|
|
|
|
|
[PA_CHANNEL_POSITION_LFE] = SPA_AUDIO_CHANNEL_LFE,
|
|
|
|
|
[PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SPA_AUDIO_CHANNEL_FLC,
|
|
|
|
|
[PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SPA_AUDIO_CHANNEL_FRC,
|
|
|
|
|
|
|
|
|
|
[PA_CHANNEL_POSITION_SIDE_LEFT] = SPA_AUDIO_CHANNEL_SL,
|
|
|
|
|
[PA_CHANNEL_POSITION_SIDE_RIGHT] = SPA_AUDIO_CHANNEL_SR,
|
|
|
|
|
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX0] = SPA_AUDIO_CHANNEL_CUSTOM_START + 1,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX1] = SPA_AUDIO_CHANNEL_CUSTOM_START + 2,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX2] = SPA_AUDIO_CHANNEL_CUSTOM_START + 3,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX3] = SPA_AUDIO_CHANNEL_CUSTOM_START + 4,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX4] = SPA_AUDIO_CHANNEL_CUSTOM_START + 5,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX5] = SPA_AUDIO_CHANNEL_CUSTOM_START + 6,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX6] = SPA_AUDIO_CHANNEL_CUSTOM_START + 7,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX7] = SPA_AUDIO_CHANNEL_CUSTOM_START + 8,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX8] = SPA_AUDIO_CHANNEL_CUSTOM_START + 9,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX9] = SPA_AUDIO_CHANNEL_CUSTOM_START + 10,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX10] = SPA_AUDIO_CHANNEL_CUSTOM_START + 11,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX11] = SPA_AUDIO_CHANNEL_CUSTOM_START + 12,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX12] = SPA_AUDIO_CHANNEL_CUSTOM_START + 13,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX13] = SPA_AUDIO_CHANNEL_CUSTOM_START + 14,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX14] = SPA_AUDIO_CHANNEL_CUSTOM_START + 15,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX15] = SPA_AUDIO_CHANNEL_CUSTOM_START + 16,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX16] = SPA_AUDIO_CHANNEL_CUSTOM_START + 17,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX17] = SPA_AUDIO_CHANNEL_CUSTOM_START + 18,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX18] = SPA_AUDIO_CHANNEL_CUSTOM_START + 19,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX19] = SPA_AUDIO_CHANNEL_CUSTOM_START + 20,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX20] = SPA_AUDIO_CHANNEL_CUSTOM_START + 21,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX21] = SPA_AUDIO_CHANNEL_CUSTOM_START + 22,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX22] = SPA_AUDIO_CHANNEL_CUSTOM_START + 23,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX23] = SPA_AUDIO_CHANNEL_CUSTOM_START + 24,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX24] = SPA_AUDIO_CHANNEL_CUSTOM_START + 25,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX25] = SPA_AUDIO_CHANNEL_CUSTOM_START + 26,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX26] = SPA_AUDIO_CHANNEL_CUSTOM_START + 27,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX27] = SPA_AUDIO_CHANNEL_CUSTOM_START + 28,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX28] = SPA_AUDIO_CHANNEL_CUSTOM_START + 29,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX29] = SPA_AUDIO_CHANNEL_CUSTOM_START + 30,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX30] = SPA_AUDIO_CHANNEL_CUSTOM_START + 31,
|
|
|
|
|
[PA_CHANNEL_POSITION_AUX31] = SPA_AUDIO_CHANNEL_CUSTOM_START + 32,
|
|
|
|
|
|
|
|
|
|
[PA_CHANNEL_POSITION_TOP_CENTER] = SPA_AUDIO_CHANNEL_TC,
|
|
|
|
|
|
|
|
|
|
[PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SPA_AUDIO_CHANNEL_TFL,
|
|
|
|
|
[PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SPA_AUDIO_CHANNEL_TFR,
|
|
|
|
|
[PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SPA_AUDIO_CHANNEL_TFC,
|
|
|
|
|
|
|
|
|
|
[PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SPA_AUDIO_CHANNEL_TRL,
|
|
|
|
|
[PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SPA_AUDIO_CHANNEL_TRR,
|
|
|
|
|
[PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SPA_AUDIO_CHANNEL_TRC,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static inline uint32_t channel_pa2id(pa_stream *s, pa_channel_position_t channel)
|
|
|
|
|
{
|
2019-01-07 15:04:34 +01:00
|
|
|
if (channel < 0 || (size_t)channel >= SPA_N_ELEMENTS(audio_channels))
|
2018-09-17 09:51:39 +02:00
|
|
|
return SPA_AUDIO_CHANNEL_UNKNOWN;
|
|
|
|
|
return audio_channels[channel];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline pa_channel_position_t channel_id2pa(pa_stream *s, uint32_t id)
|
|
|
|
|
{
|
2019-01-07 15:04:34 +01:00
|
|
|
size_t i;
|
2018-09-17 09:51:39 +02:00
|
|
|
for (i = 0; i < SPA_N_ELEMENTS(audio_channels); i++) {
|
|
|
|
|
if (id == audio_channels[i])
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
return PA_CHANNEL_POSITION_INVALID;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-19 13:35:46 +02:00
|
|
|
static inline int dequeue_buffer(pa_stream *s)
|
2018-06-22 17:41:12 +02:00
|
|
|
{
|
|
|
|
|
struct pw_buffer *buf;
|
|
|
|
|
uint32_t index;
|
|
|
|
|
|
|
|
|
|
buf = pw_stream_dequeue_buffer(s->stream);
|
|
|
|
|
if (buf == NULL)
|
|
|
|
|
return -EPIPE;
|
|
|
|
|
|
|
|
|
|
spa_ringbuffer_get_write_index(&s->dequeued_ring, &index);
|
|
|
|
|
s->dequeued[index & MASK_BUFFERS] = buf;
|
|
|
|
|
if (s->direction == PA_STREAM_PLAYBACK)
|
|
|
|
|
s->dequeued_size += buf->buffer->datas[0].maxsize;
|
|
|
|
|
else
|
|
|
|
|
s->dequeued_size += buf->buffer->datas[0].chunk->size;
|
2018-07-03 22:03:25 +02:00
|
|
|
spa_ringbuffer_write_update(&s->dequeued_ring, index + 1);
|
2018-06-22 17:41:12 +02:00
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-04 15:33:20 +02:00
|
|
|
static void dump_buffer_attr(pa_stream *s, pa_buffer_attr *attr)
|
|
|
|
|
{
|
|
|
|
|
pw_log_info("stream %p: maxlength: %u", s, attr->maxlength);
|
|
|
|
|
pw_log_info("stream %p: tlength: %u", s, attr->tlength);
|
|
|
|
|
pw_log_info("stream %p: minreq: %u", s, attr->minreq);
|
|
|
|
|
pw_log_info("stream %p: prebuf: %u", s, attr->prebuf);
|
|
|
|
|
pw_log_info("stream %p: fragsize: %u", s, attr->fragsize);
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-22 17:41:12 +02:00
|
|
|
static void configure_buffers(pa_stream *s)
|
|
|
|
|
{
|
2018-07-04 15:33:20 +02:00
|
|
|
s->buffer_attr.maxlength = s->maxsize;
|
2019-01-07 15:04:34 +01:00
|
|
|
if (s->buffer_attr.prebuf == (uint32_t)-1)
|
2018-09-21 16:46:51 +02:00
|
|
|
s->buffer_attr.prebuf = s->buffer_attr.minreq;
|
2018-07-04 15:33:20 +02:00
|
|
|
s->buffer_attr.fragsize = s->buffer_attr.minreq;
|
|
|
|
|
dump_buffer_attr(s, &s->buffer_attr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void configure_device(pa_stream *s)
|
|
|
|
|
{
|
|
|
|
|
struct global *g;
|
|
|
|
|
const char *str;
|
|
|
|
|
|
2018-11-28 11:13:21 +01:00
|
|
|
g = pa_context_find_linked(s->context, pa_stream_get_index(s));
|
2018-07-04 15:33:20 +02:00
|
|
|
if (g == NULL) {
|
|
|
|
|
s->device_index = PA_INVALID_INDEX;
|
|
|
|
|
s->device_name = NULL;
|
|
|
|
|
}
|
|
|
|
|
else {
|
2019-07-02 17:38:16 +02:00
|
|
|
if (s->direction == PA_STREAM_RECORD) {
|
|
|
|
|
if (g->mask == (PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE))
|
2019-11-07 11:45:16 +01:00
|
|
|
s->device_index = g->node_info.monitor;
|
2019-07-02 17:38:16 +02:00
|
|
|
else
|
|
|
|
|
s->device_index = g->id;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
s->device_index = g->id;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-24 15:47:05 +02:00
|
|
|
if ((str = pw_properties_get(g->props, PW_KEY_NODE_NAME)) == NULL)
|
2018-07-04 15:33:20 +02:00
|
|
|
s->device_name = strdup("unknown");
|
|
|
|
|
else
|
|
|
|
|
s->device_name = strdup(str);
|
|
|
|
|
}
|
2018-12-11 16:37:30 +01:00
|
|
|
pw_log_debug("stream %p: linked to %d '%s'", s, s->device_index, s->device_name);
|
2018-06-22 17:41:12 +02:00
|
|
|
}
|
2018-06-01 11:28:31 +02:00
|
|
|
|
|
|
|
|
static void stream_state_changed(void *data, enum pw_stream_state old,
|
|
|
|
|
enum pw_stream_state state, const char *error)
|
|
|
|
|
{
|
|
|
|
|
pa_stream *s = data;
|
|
|
|
|
|
2018-12-14 16:43:34 +01:00
|
|
|
pw_log_debug("stream %p: state '%s'->'%s'", s, pw_stream_state_as_string(old),
|
|
|
|
|
pw_stream_state_as_string(state));
|
|
|
|
|
|
2019-03-08 17:01:38 +01:00
|
|
|
if (s->state == PA_STREAM_TERMINATED)
|
|
|
|
|
return;
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
switch(state) {
|
|
|
|
|
case PW_STREAM_STATE_ERROR:
|
|
|
|
|
pa_stream_set_state(s, PA_STREAM_FAILED);
|
|
|
|
|
break;
|
|
|
|
|
case PW_STREAM_STATE_UNCONNECTED:
|
2018-12-14 16:43:34 +01:00
|
|
|
if (!s->disconnecting) {
|
|
|
|
|
pa_context_set_error(s->context, PA_ERR_KILLED);
|
|
|
|
|
pa_stream_set_state(s, PA_STREAM_FAILED);
|
|
|
|
|
} else {
|
|
|
|
|
pa_stream_set_state(s, PA_STREAM_TERMINATED);
|
|
|
|
|
}
|
2018-06-01 11:28:31 +02:00
|
|
|
break;
|
|
|
|
|
case PW_STREAM_STATE_CONNECTING:
|
|
|
|
|
pa_stream_set_state(s, PA_STREAM_CREATING);
|
|
|
|
|
break;
|
|
|
|
|
case PW_STREAM_STATE_PAUSED:
|
2019-11-21 16:14:50 +01:00
|
|
|
if (!s->suspended && s->suspended_callback) {
|
|
|
|
|
s->suspended = true;
|
|
|
|
|
s->suspended_callback(s, s->suspended_userdata);
|
2018-12-14 16:43:34 +01:00
|
|
|
}
|
2018-06-01 11:28:31 +02:00
|
|
|
break;
|
|
|
|
|
case PW_STREAM_STATE_STREAMING:
|
2019-11-21 16:14:50 +01:00
|
|
|
if (s->suspended && s->suspended_callback) {
|
|
|
|
|
s->suspended = false;
|
|
|
|
|
s->suspended_callback(s, s->suspended_userdata);
|
|
|
|
|
}
|
|
|
|
|
configure_device(s);
|
|
|
|
|
configure_buffers(s);
|
|
|
|
|
pa_stream_set_state(s, PA_STREAM_READY);
|
2018-06-01 11:28:31 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-22 17:41:12 +02:00
|
|
|
static const struct spa_pod *get_buffers_param(pa_stream *s, pa_buffer_attr *attr, struct spa_pod_builder *b)
|
|
|
|
|
{
|
|
|
|
|
const struct spa_pod *param;
|
2019-01-07 15:04:34 +01:00
|
|
|
uint32_t blocks, buffers, size, maxsize, stride;
|
2018-06-22 17:41:12 +02:00
|
|
|
|
|
|
|
|
blocks = 1;
|
|
|
|
|
stride = pa_frame_size(&s->sample_spec);
|
|
|
|
|
|
2019-01-07 15:04:34 +01:00
|
|
|
if (attr->tlength == (uint32_t)-1 || attr->tlength == 0)
|
2018-06-22 17:41:12 +02:00
|
|
|
maxsize = 1024;
|
|
|
|
|
else
|
|
|
|
|
maxsize = (attr->tlength / stride);
|
|
|
|
|
|
2019-01-07 15:04:34 +01:00
|
|
|
if (attr->minreq == (uint32_t)-1 || attr->minreq == 0)
|
2018-10-19 13:30:20 +02:00
|
|
|
size = maxsize;
|
2018-06-22 17:41:12 +02:00
|
|
|
else
|
|
|
|
|
size = SPA_MIN(attr->minreq / stride, maxsize);
|
|
|
|
|
|
2019-01-07 15:04:34 +01:00
|
|
|
if (attr->maxlength == (uint32_t)-1)
|
2018-06-22 17:41:12 +02:00
|
|
|
buffers = 3;
|
|
|
|
|
else
|
2019-01-07 15:04:34 +01:00
|
|
|
buffers = SPA_CLAMP(attr->maxlength / (size * stride), 3u, MAX_BUFFERS);
|
2018-06-22 17:41:12 +02:00
|
|
|
|
2018-06-26 15:04:08 +02:00
|
|
|
pw_log_info("stream %p: stride %d maxsize %d size %u buffers %d", s, stride, maxsize,
|
|
|
|
|
size, buffers);
|
|
|
|
|
|
2019-01-16 11:04:22 +01:00
|
|
|
param = spa_pod_builder_add_object(b,
|
2018-08-29 09:54:54 +02:00
|
|
|
SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
|
2019-08-13 18:47:30 +02:00
|
|
|
SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers, buffers, MAX_BUFFERS),
|
2019-01-16 11:04:22 +01:00
|
|
|
SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks),
|
|
|
|
|
SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(
|
2018-09-05 16:48:13 +02:00
|
|
|
size * stride,
|
|
|
|
|
size * stride,
|
|
|
|
|
maxsize * stride),
|
2019-01-16 11:04:22 +01:00
|
|
|
SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride),
|
|
|
|
|
SPA_PARAM_BUFFERS_align, SPA_POD_Int(16));
|
2018-06-22 17:41:12 +02:00
|
|
|
return param;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-26 15:04:08 +02:00
|
|
|
static void patch_buffer_attr(pa_stream *s, pa_buffer_attr *attr, pa_stream_flags_t *flags) {
|
|
|
|
|
const char *e;
|
|
|
|
|
|
|
|
|
|
pa_assert(s);
|
|
|
|
|
pa_assert(attr);
|
|
|
|
|
|
|
|
|
|
if ((e = getenv("PULSE_LATENCY_MSEC"))) {
|
|
|
|
|
uint32_t ms;
|
|
|
|
|
pa_sample_spec ss;
|
|
|
|
|
|
|
|
|
|
pa_sample_spec_init(&ss);
|
|
|
|
|
|
|
|
|
|
if (pa_sample_spec_valid(&s->sample_spec))
|
|
|
|
|
ss = s->sample_spec;
|
|
|
|
|
else if (s->n_formats == 1)
|
|
|
|
|
pa_format_info_to_sample_spec(s->req_formats[0], &ss, NULL);
|
|
|
|
|
|
2019-01-07 15:04:34 +01:00
|
|
|
if ((ms = atoi(e)) == 0) {
|
2018-06-26 15:04:08 +02:00
|
|
|
pa_log_debug("Failed to parse $PULSE_LATENCY_MSEC: %s", e);
|
|
|
|
|
}
|
|
|
|
|
else if (!pa_sample_spec_valid(&s->sample_spec)) {
|
|
|
|
|
pa_log_debug("Ignoring $PULSE_LATENCY_MSEC: %s (invalid sample spec)", e);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
attr->maxlength = (uint32_t) -1;
|
|
|
|
|
attr->tlength = pa_usec_to_bytes(ms * PA_USEC_PER_MSEC, &ss);
|
|
|
|
|
attr->minreq = (uint32_t) -1;
|
|
|
|
|
attr->prebuf = (uint32_t) -1;
|
|
|
|
|
attr->fragsize = attr->tlength;
|
|
|
|
|
|
|
|
|
|
if (flags)
|
|
|
|
|
*flags |= PA_STREAM_ADJUST_LATENCY;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (attr->maxlength == (uint32_t) -1)
|
|
|
|
|
attr->maxlength = 4*1024*1024; /* 4MB is the maximum queue length PulseAudio <= 0.9.9 supported. */
|
|
|
|
|
|
|
|
|
|
if (attr->tlength == (uint32_t) -1)
|
|
|
|
|
attr->tlength = (uint32_t) pa_usec_to_bytes(250*PA_USEC_PER_MSEC, &s->sample_spec); /* 250ms of buffering */
|
|
|
|
|
|
|
|
|
|
if (attr->minreq == (uint32_t) -1)
|
2018-07-03 22:03:25 +02:00
|
|
|
attr->minreq = attr->tlength; /* Ask for more data when there are only 200ms left in the playback buffer */
|
2018-06-26 15:04:08 +02:00
|
|
|
|
|
|
|
|
if (attr->prebuf == (uint32_t) -1)
|
|
|
|
|
attr->prebuf = attr->tlength; /* Start to play only when the playback is fully filled up once */
|
|
|
|
|
|
|
|
|
|
if (attr->fragsize == (uint32_t) -1)
|
|
|
|
|
attr->fragsize = attr->tlength; /* Pass data to the app only when the buffer is filled up once */
|
|
|
|
|
|
2018-07-04 15:33:20 +02:00
|
|
|
dump_buffer_attr(s, attr);
|
2018-06-26 15:04:08 +02:00
|
|
|
}
|
|
|
|
|
|
2019-11-21 16:14:50 +01:00
|
|
|
static void stream_param_changed(void *data, uint32_t id, const struct spa_pod *param)
|
2018-06-01 11:28:31 +02:00
|
|
|
{
|
2018-06-05 20:10:31 +02:00
|
|
|
pa_stream *s = data;
|
2018-06-22 17:41:12 +02:00
|
|
|
const struct spa_pod *params[4];
|
|
|
|
|
uint32_t n_params = 0;
|
|
|
|
|
uint8_t buffer[4096];
|
|
|
|
|
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
|
|
|
|
struct spa_audio_info info = { 0 };
|
2019-01-07 15:04:34 +01:00
|
|
|
unsigned int i;
|
2018-06-22 17:41:12 +02:00
|
|
|
|
2019-11-21 16:14:50 +01:00
|
|
|
if (param == NULL || id != SPA_PARAM_Format)
|
|
|
|
|
return;
|
2018-12-04 11:48:15 +01:00
|
|
|
|
2019-11-21 16:14:50 +01:00
|
|
|
spa_format_parse(param, &info.media_type, &info.media_subtype);
|
2018-06-01 11:28:31 +02:00
|
|
|
|
2018-08-24 11:00:33 +02:00
|
|
|
if (info.media_type != SPA_MEDIA_TYPE_audio ||
|
|
|
|
|
info.media_subtype != SPA_MEDIA_SUBTYPE_raw ||
|
2019-11-21 16:14:50 +01:00
|
|
|
spa_format_audio_raw_parse(param, &info.info.raw) < 0 ||
|
2018-09-17 09:51:39 +02:00
|
|
|
!SPA_AUDIO_FORMAT_IS_INTERLEAVED(info.info.raw.format)) {
|
2019-11-21 16:14:50 +01:00
|
|
|
pw_stream_set_error(s->stream, -EINVAL, "unhandled format");
|
|
|
|
|
return;
|
2018-06-22 17:41:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s->sample_spec.format = format_id2pa(s, info.info.raw.format);
|
|
|
|
|
if (s->sample_spec.format == PA_SAMPLE_INVALID) {
|
2019-11-21 16:14:50 +01:00
|
|
|
pw_stream_set_error(s->stream, -EINVAL, "invalid format");
|
|
|
|
|
return;
|
2018-06-22 17:41:12 +02:00
|
|
|
}
|
|
|
|
|
s->sample_spec.rate = info.info.raw.rate;
|
|
|
|
|
s->sample_spec.channels = info.info.raw.channels;
|
2018-06-05 20:10:31 +02:00
|
|
|
|
2018-09-17 09:51:39 +02:00
|
|
|
pa_channel_map_init(&s->channel_map);
|
|
|
|
|
s->channel_map.channels = info.info.raw.channels;
|
|
|
|
|
for (i = 0; i < info.info.raw.channels; i++)
|
|
|
|
|
s->channel_map.map[i] = channel_id2pa(s, info.info.raw.position[i]);
|
2018-07-04 18:43:45 +02:00
|
|
|
|
2018-09-19 17:30:59 +02:00
|
|
|
if (!pa_channel_map_valid(&s->channel_map))
|
|
|
|
|
pa_channel_map_init_auto(&s->channel_map, info.info.raw.channels, PA_CHANNEL_MAP_DEFAULT);
|
|
|
|
|
|
2018-06-05 20:10:31 +02:00
|
|
|
if (s->format)
|
|
|
|
|
pa_format_info_free(s->format);
|
2018-07-04 18:43:45 +02:00
|
|
|
s->format = pa_format_info_from_sample_spec(&s->sample_spec, &s->channel_map);
|
2018-06-22 17:41:12 +02:00
|
|
|
|
2018-06-26 15:04:08 +02:00
|
|
|
patch_buffer_attr(s, &s->buffer_attr, NULL);
|
|
|
|
|
|
2018-06-22 17:41:12 +02:00
|
|
|
params[n_params++] = get_buffers_param(s, &s->buffer_attr, &b);
|
|
|
|
|
|
2019-11-21 16:14:50 +01:00
|
|
|
pw_stream_update_params(s->stream, params, n_params);
|
2018-06-05 20:10:31 +02:00
|
|
|
}
|
2018-06-01 11:28:31 +02:00
|
|
|
|
2019-08-13 18:46:27 +02:00
|
|
|
static void stream_control_info(void *data, uint32_t id, const struct pw_stream_control *control)
|
|
|
|
|
{
|
|
|
|
|
pa_stream *s = data;
|
|
|
|
|
|
|
|
|
|
switch (id) {
|
|
|
|
|
case SPA_PROP_mute:
|
|
|
|
|
if (control->n_values > 0)
|
|
|
|
|
s->mute = control->values[0] >= 0.5f;
|
|
|
|
|
break;
|
|
|
|
|
case SPA_PROP_channelVolumes:
|
|
|
|
|
s->n_channel_volumes = SPA_MAX(SPA_AUDIO_MAX_CHANNELS, control->n_values);
|
|
|
|
|
memcpy(s->channel_volumes, control->values, s->n_channel_volumes * sizeof(float));
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-04 15:33:20 +02:00
|
|
|
static void stream_add_buffer(void *data, struct pw_buffer *buffer)
|
|
|
|
|
{
|
|
|
|
|
pa_stream *s = data;
|
|
|
|
|
s->maxsize += buffer->buffer->datas[0].maxsize;
|
|
|
|
|
}
|
|
|
|
|
static void stream_remove_buffer(void *data, struct pw_buffer *buffer)
|
|
|
|
|
{
|
|
|
|
|
pa_stream *s = data;
|
|
|
|
|
s->maxsize -= buffer->buffer->datas[0].maxsize;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-19 13:35:46 +02:00
|
|
|
static void update_timing_info(pa_stream *s)
|
|
|
|
|
{
|
|
|
|
|
struct pw_time pwt;
|
|
|
|
|
pa_timing_info *ti = &s->timing_info;
|
|
|
|
|
size_t stride = pa_frame_size(&s->sample_spec);
|
2018-11-09 15:25:39 +01:00
|
|
|
int64_t delay, queued, ticks;
|
2018-07-19 13:35:46 +02:00
|
|
|
|
|
|
|
|
pw_stream_get_time(s->stream, &pwt);
|
|
|
|
|
s->timing_info_valid = false;
|
2018-08-02 10:31:29 +02:00
|
|
|
s->queued = pwt.queued;
|
2018-08-15 21:46:50 +02:00
|
|
|
pw_log_trace("stream %p: %"PRIu64, s, s->queued);
|
2018-07-19 13:35:46 +02:00
|
|
|
|
2018-08-15 21:46:50 +02:00
|
|
|
if (pwt.rate.denom == 0)
|
2018-07-19 13:35:46 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
pa_timeval_store(&ti->timestamp, pwt.now / SPA_NSEC_PER_USEC);
|
|
|
|
|
ti->synchronized_clocks = true;
|
|
|
|
|
ti->transport_usec = 0;
|
|
|
|
|
ti->playing = 1;
|
|
|
|
|
ti->write_index_corrupt = false;
|
|
|
|
|
ti->read_index_corrupt = false;
|
2018-07-31 21:37:41 +02:00
|
|
|
|
2018-11-09 15:25:39 +01:00
|
|
|
queued = pwt.queued + (pwt.ticks * s->sample_spec.rate / pwt.rate.denom) * stride;
|
|
|
|
|
ticks = ((pwt.ticks + pwt.delay) * s->sample_spec.rate / pwt.rate.denom) * stride;
|
|
|
|
|
|
2018-08-15 21:46:50 +02:00
|
|
|
delay = pwt.delay * SPA_USEC_PER_SEC / pwt.rate.denom;
|
2018-07-31 21:37:41 +02:00
|
|
|
if (s->direction == PA_STREAM_PLAYBACK) {
|
2018-11-09 15:25:39 +01:00
|
|
|
ti->sink_usec = -delay;
|
|
|
|
|
ti->write_index = queued;
|
|
|
|
|
ti->read_index = ticks;
|
2018-07-31 21:37:41 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
ti->source_usec = delay;
|
2018-11-09 15:25:39 +01:00
|
|
|
ti->read_index = queued;
|
|
|
|
|
ti->write_index = ticks;
|
2018-07-31 21:37:41 +02:00
|
|
|
}
|
2018-07-19 13:35:46 +02:00
|
|
|
|
|
|
|
|
ti->configured_sink_usec = 0;
|
|
|
|
|
ti->configured_source_usec = 0;
|
|
|
|
|
ti->since_underrun = 0;
|
|
|
|
|
s->timing_info_valid = true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
static void stream_process(void *data)
|
|
|
|
|
{
|
|
|
|
|
pa_stream *s = data;
|
|
|
|
|
|
2018-07-19 13:35:46 +02:00
|
|
|
update_timing_info(s);
|
|
|
|
|
|
|
|
|
|
while (dequeue_buffer(s) == 0);
|
2018-06-05 20:10:31 +02:00
|
|
|
|
2018-07-19 13:35:46 +02:00
|
|
|
if (s->dequeued_size <= 0)
|
2018-06-07 11:16:09 +02:00
|
|
|
return;
|
2018-06-05 20:10:31 +02:00
|
|
|
|
2018-06-07 11:16:09 +02:00
|
|
|
if (s->direction == PA_STREAM_PLAYBACK) {
|
2018-06-01 11:28:31 +02:00
|
|
|
if (s->write_callback)
|
2018-06-05 20:10:31 +02:00
|
|
|
s->write_callback(s, s->dequeued_size, s->write_userdata);
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (s->read_callback)
|
2018-06-07 11:16:09 +02:00
|
|
|
s->read_callback(s, s->dequeued_size, s->read_userdata);
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-16 15:23:05 +02:00
|
|
|
static void stream_drained(void *data)
|
|
|
|
|
{
|
|
|
|
|
pa_stream *s = data;
|
|
|
|
|
|
|
|
|
|
if (s->drain) {
|
|
|
|
|
pa_operation *o = s->drain;
|
|
|
|
|
pa_operation_ref(o);
|
|
|
|
|
if (o->callback)
|
|
|
|
|
o->callback(o, o->userdata);
|
|
|
|
|
pa_operation_unref(o);
|
|
|
|
|
s->drain = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
static const struct pw_stream_events stream_events =
|
|
|
|
|
{
|
|
|
|
|
PW_VERSION_STREAM_EVENTS,
|
|
|
|
|
.state_changed = stream_state_changed,
|
2019-11-21 16:14:50 +01:00
|
|
|
.param_changed = stream_param_changed,
|
2019-08-13 18:46:27 +02:00
|
|
|
.control_info = stream_control_info,
|
2018-07-04 15:33:20 +02:00
|
|
|
.add_buffer = stream_add_buffer,
|
|
|
|
|
.remove_buffer = stream_remove_buffer,
|
2018-06-01 11:28:31 +02:00
|
|
|
.process = stream_process,
|
2019-08-16 15:23:05 +02:00
|
|
|
.drained = stream_drained,
|
2018-06-01 11:28:31 +02:00
|
|
|
};
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
static pa_stream* stream_new(pa_context *c, const char *name,
|
2018-06-01 11:28:31 +02:00
|
|
|
const pa_sample_spec *ss, const pa_channel_map *map,
|
|
|
|
|
pa_format_info * const * formats, unsigned int n_formats,
|
|
|
|
|
pa_proplist *p)
|
|
|
|
|
{
|
|
|
|
|
pa_stream *s;
|
2018-09-17 09:51:39 +02:00
|
|
|
char str[1024];
|
2019-01-07 15:04:34 +01:00
|
|
|
unsigned int i;
|
2018-10-03 20:14:34 +02:00
|
|
|
struct pw_properties *props;
|
2018-06-01 11:28:31 +02:00
|
|
|
|
|
|
|
|
spa_assert(c);
|
|
|
|
|
spa_assert(c->refcount >= 1);
|
|
|
|
|
pa_assert((ss == NULL && map == NULL) || (formats == NULL && n_formats == 0));
|
|
|
|
|
pa_assert(n_formats < PA_MAX_FORMATS);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(c, name ||
|
|
|
|
|
(p && pa_proplist_contains(p, PA_PROP_MEDIA_NAME)), PA_ERR_INVALID);
|
|
|
|
|
|
|
|
|
|
s = calloc(1, sizeof(pa_stream));
|
|
|
|
|
if (s == NULL)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
2018-10-03 20:14:34 +02:00
|
|
|
s->proplist = p ? pa_proplist_copy(p) : pa_proplist_new();
|
|
|
|
|
if (name)
|
|
|
|
|
pa_proplist_sets(s->proplist, PA_PROP_MEDIA_NAME, name);
|
|
|
|
|
else
|
2018-12-04 11:48:15 +01:00
|
|
|
name = pa_proplist_gets(s->proplist, PA_PROP_MEDIA_NAME);
|
2018-10-03 20:14:34 +02:00
|
|
|
|
2019-05-24 15:47:05 +02:00
|
|
|
props = pw_properties_new(PW_KEY_CLIENT_API, "pulseaudio",
|
2018-10-03 20:14:34 +02:00
|
|
|
NULL);
|
2018-12-04 11:48:15 +01:00
|
|
|
pw_properties_update(props, &s->proplist->props->dict);
|
2018-06-07 11:16:09 +02:00
|
|
|
|
2018-10-03 20:14:34 +02:00
|
|
|
s->stream = pw_stream_new(c->remote, name, props);
|
2018-06-01 11:28:31 +02:00
|
|
|
s->refcount = 1;
|
|
|
|
|
s->context = c;
|
2018-06-26 16:57:49 +02:00
|
|
|
spa_list_init(&s->pending);
|
2018-06-01 11:28:31 +02:00
|
|
|
|
|
|
|
|
pw_stream_add_listener(s->stream, &s->stream_listener, &stream_events, s);
|
|
|
|
|
|
|
|
|
|
s->direction = PA_STREAM_NODIRECTION;
|
|
|
|
|
s->state = PA_STREAM_UNCONNECTED;
|
|
|
|
|
s->flags = 0;
|
|
|
|
|
|
|
|
|
|
if (ss)
|
|
|
|
|
s->sample_spec = *ss;
|
|
|
|
|
else
|
|
|
|
|
pa_sample_spec_init(&s->sample_spec);
|
|
|
|
|
|
|
|
|
|
if (map)
|
|
|
|
|
s->channel_map = *map;
|
|
|
|
|
else
|
|
|
|
|
pa_channel_map_init(&s->channel_map);
|
|
|
|
|
|
2018-09-17 09:51:39 +02:00
|
|
|
pw_log_debug("channel map: %p %s", map, pa_channel_map_snprint(str, sizeof(str), &s->channel_map));
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
s->n_formats = 0;
|
|
|
|
|
if (formats) {
|
|
|
|
|
s->n_formats = n_formats;
|
|
|
|
|
for (i = 0; i < n_formats; i++)
|
|
|
|
|
s->req_formats[i] = pa_format_info_copy(formats[i]);
|
|
|
|
|
}
|
|
|
|
|
s->format = NULL;
|
|
|
|
|
|
|
|
|
|
s->direct_on_input = PA_INVALID_INDEX;
|
|
|
|
|
|
|
|
|
|
s->stream_index = PA_INVALID_INDEX;
|
|
|
|
|
|
|
|
|
|
s->buffer_attr.maxlength = (uint32_t) -1;
|
|
|
|
|
if (ss)
|
|
|
|
|
s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes(250*PA_USEC_PER_MSEC, ss); /* 250ms of buffering */
|
|
|
|
|
else {
|
|
|
|
|
/* FIXME: We assume a worst-case compressed format corresponding to
|
|
|
|
|
* 48000 Hz, 2 ch, S16 PCM, but this can very well be incorrect */
|
|
|
|
|
pa_sample_spec tmp_ss = {
|
|
|
|
|
.format = PA_SAMPLE_S16NE,
|
|
|
|
|
.rate = 48000,
|
|
|
|
|
.channels = 2,
|
|
|
|
|
};
|
|
|
|
|
s->buffer_attr.tlength = (uint32_t) pa_usec_to_bytes(250*PA_USEC_PER_MSEC, &tmp_ss); /* 250ms of buffering */
|
|
|
|
|
}
|
|
|
|
|
s->buffer_attr.minreq = (uint32_t) -1;
|
|
|
|
|
s->buffer_attr.prebuf = (uint32_t) -1;
|
|
|
|
|
s->buffer_attr.fragsize = (uint32_t) -1;
|
|
|
|
|
|
|
|
|
|
s->device_index = PA_INVALID_INDEX;
|
2018-07-04 15:33:20 +02:00
|
|
|
s->device_name = NULL;
|
2018-06-22 17:41:12 +02:00
|
|
|
|
2018-06-05 20:10:31 +02:00
|
|
|
spa_ringbuffer_init(&s->dequeued_ring);
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_list_append(&c->streams, &s->link);
|
|
|
|
|
pa_stream_ref(s);
|
|
|
|
|
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
pa_stream* pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *ss,
|
|
|
|
|
const pa_channel_map *map)
|
|
|
|
|
{
|
|
|
|
|
return stream_new(c, name, ss, map, NULL, 0, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
pa_stream* pa_stream_new_with_proplist(pa_context *c, const char *name,
|
|
|
|
|
const pa_sample_spec *ss, const pa_channel_map *map, pa_proplist *p)
|
|
|
|
|
{
|
2018-10-09 16:37:23 +02:00
|
|
|
pa_channel_map tmap;
|
|
|
|
|
|
|
|
|
|
if (!map)
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(c, map = pa_channel_map_init_auto(&tmap, ss->channels, PA_CHANNEL_MAP_DEFAULT), PA_ERR_INVALID);
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
return stream_new(c, name, ss, map, NULL, 0, p);
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
pa_stream *pa_stream_new_extended(pa_context *c, const char *name,
|
|
|
|
|
pa_format_info * const * formats, unsigned int n_formats, pa_proplist *p)
|
|
|
|
|
{
|
|
|
|
|
return stream_new(c, name, NULL, NULL, formats, n_formats, p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void stream_unlink(pa_stream *s)
|
|
|
|
|
{
|
2018-12-14 16:43:34 +01:00
|
|
|
pa_context *c = s->context;
|
|
|
|
|
pa_operation *o, *t;
|
|
|
|
|
|
|
|
|
|
if (c == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
pw_log_debug("stream %p: unlink %d", s, s->refcount);
|
|
|
|
|
|
|
|
|
|
spa_list_for_each_safe(o, t, &c->operations, link) {
|
|
|
|
|
if (o->stream == s)
|
|
|
|
|
pa_operation_cancel(o);
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_list_remove(&s->link);
|
2019-04-17 15:24:47 +02:00
|
|
|
pw_stream_set_active(s->stream, false);
|
2018-12-14 16:43:34 +01:00
|
|
|
|
|
|
|
|
s->context = NULL;
|
|
|
|
|
pa_stream_unref(s);
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void stream_free(pa_stream *s)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
|
2018-12-14 16:43:34 +01:00
|
|
|
pw_log_debug("stream %p", s);
|
|
|
|
|
|
|
|
|
|
if (s->stream) {
|
|
|
|
|
spa_hook_remove(&s->stream_listener);
|
|
|
|
|
pw_stream_destroy(s->stream);
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
if (s->proplist)
|
|
|
|
|
pa_proplist_free(s->proplist);
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < s->n_formats; i++)
|
|
|
|
|
pa_format_info_free(s->req_formats[i]);
|
|
|
|
|
|
|
|
|
|
if (s->format)
|
|
|
|
|
pa_format_info_free(s->format);
|
|
|
|
|
|
|
|
|
|
free(s->device_name);
|
|
|
|
|
free(s);
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
void pa_stream_unref(pa_stream *s)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
if (--s->refcount == 0)
|
|
|
|
|
stream_free(s);
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
pa_stream *pa_stream_ref(pa_stream *s)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
s->refcount++;
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2019-09-27 07:45:15 +05:30
|
|
|
pa_stream_state_t pa_stream_get_state(PA_CONST pa_stream *s)
|
2018-06-01 11:28:31 +02:00
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
return s->state;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2019-09-27 07:45:15 +05:30
|
|
|
pa_context* pa_stream_get_context(PA_CONST pa_stream *s)
|
2018-06-01 11:28:31 +02:00
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
return s->context;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2019-09-27 07:45:15 +05:30
|
|
|
uint32_t pa_stream_get_index(PA_CONST pa_stream *s)
|
2018-06-01 11:28:31 +02:00
|
|
|
{
|
2019-03-15 20:31:20 +01:00
|
|
|
uint32_t idx;
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
2019-03-15 20:31:20 +01:00
|
|
|
idx = pw_stream_get_node_id(s->stream);
|
|
|
|
|
pw_log_debug("stream %p: index %u", s, idx);
|
|
|
|
|
return idx;
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pa_stream_set_state(pa_stream *s, pa_stream_state_t st) {
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
if (s->state == st)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
pa_stream_ref(s);
|
|
|
|
|
|
2018-07-04 15:33:20 +02:00
|
|
|
pw_log_debug("stream %p: state %d -> %d", s, s->state, st);
|
2018-06-01 11:28:31 +02:00
|
|
|
s->state = st;
|
|
|
|
|
|
|
|
|
|
if (s->state_callback)
|
|
|
|
|
s->state_callback(s, s->state_userdata);
|
|
|
|
|
|
|
|
|
|
if ((st == PA_STREAM_FAILED || st == PA_STREAM_TERMINATED))
|
|
|
|
|
stream_unlink(s);
|
|
|
|
|
|
|
|
|
|
pa_stream_unref(s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2019-09-27 07:45:15 +05:30
|
|
|
uint32_t pa_stream_get_device_index(PA_CONST pa_stream *s)
|
2018-06-01 11:28:31 +02:00
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY,
|
|
|
|
|
PA_ERR_BADSTATE, PA_INVALID_INDEX);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction != PA_STREAM_UPLOAD,
|
|
|
|
|
PA_ERR_BADSTATE, PA_INVALID_INDEX);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->device_index != PA_INVALID_INDEX,
|
|
|
|
|
PA_ERR_BADSTATE, PA_INVALID_INDEX);
|
|
|
|
|
|
2018-12-11 16:37:30 +01:00
|
|
|
pw_log_trace("stream %p: %d", s, s->device_index);
|
2018-06-01 11:28:31 +02:00
|
|
|
return s->device_index;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2019-09-27 07:45:15 +05:30
|
|
|
const char *pa_stream_get_device_name(PA_CONST pa_stream *s)
|
2018-06-01 11:28:31 +02:00
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
2019-08-16 22:10:08 +02:00
|
|
|
// PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->device_name, PA_ERR_BADSTATE);
|
|
|
|
|
|
|
|
|
|
if (s->device_name == NULL)
|
|
|
|
|
return "unnamed";
|
2018-06-01 11:28:31 +02:00
|
|
|
|
|
|
|
|
return s->device_name;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2019-09-27 07:45:15 +05:30
|
|
|
int pa_stream_is_suspended(PA_CONST pa_stream *s)
|
2018-06-01 11:28:31 +02:00
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
|
|
|
|
|
|
|
|
|
return s->suspended;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2019-09-27 07:45:15 +05:30
|
|
|
int pa_stream_is_corked(PA_CONST pa_stream *s)
|
2018-06-01 11:28:31 +02:00
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
|
|
|
|
|
2018-09-05 16:48:13 +02:00
|
|
|
pw_log_trace("stream %p: corked %d", s, s->corked);
|
2018-06-01 11:28:31 +02:00
|
|
|
return s->corked;
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-07 11:16:09 +02:00
|
|
|
static const struct spa_pod *get_param(pa_stream *s, pa_sample_spec *ss, pa_channel_map *map,
|
|
|
|
|
struct spa_pod_builder *b)
|
|
|
|
|
{
|
2018-09-17 09:51:39 +02:00
|
|
|
struct spa_audio_info_raw info;
|
2018-09-05 16:48:13 +02:00
|
|
|
|
2018-09-17 09:51:39 +02:00
|
|
|
info = SPA_AUDIO_INFO_RAW_INIT( .format = format_pa2id(s, ss->format),
|
2018-09-05 16:48:13 +02:00
|
|
|
.channels = ss->channels,
|
2018-09-17 09:51:39 +02:00
|
|
|
.rate = ss->rate);
|
|
|
|
|
if (map) {
|
|
|
|
|
int i;
|
|
|
|
|
for (i = 0; i < map->channels; i++)
|
|
|
|
|
info.position[i] = channel_pa2id(s, map->map[i]);
|
|
|
|
|
}
|
|
|
|
|
return spa_format_audio_raw_build(b, SPA_PARAM_EnumFormat, &info);
|
2018-06-07 11:16:09 +02:00
|
|
|
}
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
static int create_stream(pa_stream_direction_t direction,
|
|
|
|
|
pa_stream *s,
|
|
|
|
|
const char *dev,
|
|
|
|
|
const pa_buffer_attr *attr,
|
|
|
|
|
pa_stream_flags_t flags,
|
|
|
|
|
const pa_cvolume *volume,
|
|
|
|
|
pa_stream *sync_stream)
|
|
|
|
|
{
|
|
|
|
|
int res;
|
|
|
|
|
enum pw_stream_flags fl;
|
2018-06-07 11:16:09 +02:00
|
|
|
const struct spa_pod *params[16];
|
2019-08-12 12:31:55 +02:00
|
|
|
uint32_t i, n_params = 0;
|
2018-06-07 11:16:09 +02:00
|
|
|
uint8_t buffer[4096];
|
2018-06-01 11:28:31 +02:00
|
|
|
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
2018-06-26 15:04:08 +02:00
|
|
|
uint32_t sample_rate = 0, stride = 0;
|
2018-07-04 18:43:45 +02:00
|
|
|
const char *str;
|
2018-11-02 12:31:43 +01:00
|
|
|
uint32_t devid;
|
2018-09-21 16:46:51 +02:00
|
|
|
struct global *g;
|
2018-10-23 16:50:17 +02:00
|
|
|
struct spa_dict_item items[5];
|
|
|
|
|
char latency[64];
|
2018-12-05 16:02:03 +01:00
|
|
|
bool monitor;
|
2018-06-01 11:28:31 +02:00
|
|
|
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
2018-12-04 11:48:15 +01:00
|
|
|
pw_log_debug("stream %p: connect %s %08x", s, dev, flags);
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
s->direction = direction;
|
2018-06-07 11:16:09 +02:00
|
|
|
s->timing_info_valid = false;
|
|
|
|
|
s->disconnecting = false;
|
2019-08-12 12:31:55 +02:00
|
|
|
if (volume) {
|
|
|
|
|
for (i = 0; i < volume->channels; i++)
|
|
|
|
|
s->channel_volumes[i] = volume->values[i] / (float) PA_VOLUME_NORM;
|
|
|
|
|
s->n_channel_volumes = volume->channels;
|
|
|
|
|
} else {
|
|
|
|
|
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++)
|
|
|
|
|
s->channel_volumes[i] = 1.0;
|
|
|
|
|
s->n_channel_volumes = 0;
|
|
|
|
|
}
|
2018-10-19 16:57:03 +02:00
|
|
|
s->mute = false;
|
2018-06-01 11:28:31 +02:00
|
|
|
|
|
|
|
|
pa_stream_set_state(s, PA_STREAM_CREATING);
|
|
|
|
|
|
|
|
|
|
fl = PW_STREAM_FLAG_AUTOCONNECT |
|
2018-06-07 11:16:09 +02:00
|
|
|
PW_STREAM_FLAG_MAP_BUFFERS;
|
|
|
|
|
|
2019-10-02 17:54:21 +02:00
|
|
|
s->corked = SPA_FLAG_IS_SET(flags, PA_STREAM_START_CORKED);
|
2018-06-01 11:28:31 +02:00
|
|
|
|
2018-06-07 11:16:09 +02:00
|
|
|
if (s->corked)
|
|
|
|
|
fl |= PW_STREAM_FLAG_INACTIVE;
|
2018-06-01 11:28:31 +02:00
|
|
|
if (flags & PA_STREAM_PASSTHROUGH)
|
|
|
|
|
fl |= PW_STREAM_FLAG_EXCLUSIVE;
|
2018-12-05 16:02:03 +01:00
|
|
|
if (flags & PA_STREAM_DONT_MOVE)
|
|
|
|
|
fl |= PW_STREAM_FLAG_DONT_RECONNECT;
|
|
|
|
|
monitor = (flags & PA_STREAM_PEAK_DETECT);
|
2018-06-01 11:28:31 +02:00
|
|
|
|
2018-06-07 11:16:09 +02:00
|
|
|
if (pa_sample_spec_valid(&s->sample_spec)) {
|
|
|
|
|
params[n_params++] = get_param(s, &s->sample_spec, &s->channel_map, &b);
|
2018-06-26 15:04:08 +02:00
|
|
|
sample_rate = s->sample_spec.rate;
|
|
|
|
|
stride = pa_frame_size(&s->sample_spec);
|
2018-06-07 11:16:09 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
pa_sample_spec ss;
|
2018-10-09 16:37:23 +02:00
|
|
|
pa_channel_map chmap;
|
2018-06-07 11:16:09 +02:00
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < s->n_formats; i++) {
|
2018-10-23 16:50:17 +02:00
|
|
|
if ((res = pa_format_info_to_sample_spec(s->req_formats[i], &ss, NULL)) < 0) {
|
2018-06-07 11:16:09 +02:00
|
|
|
char buf[4096];
|
2018-06-26 15:04:08 +02:00
|
|
|
pw_log_warn("can't convert format %d %s", res,
|
2018-06-07 11:16:09 +02:00
|
|
|
pa_format_info_snprint(buf,4096,s->req_formats[i]));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2018-10-23 16:50:17 +02:00
|
|
|
if (pa_format_info_get_channel_map(s->req_formats[i], &chmap) < 0)
|
|
|
|
|
pa_channel_map_init_auto(&chmap, ss.channels, PA_CHANNEL_MAP_DEFAULT);
|
2018-06-07 11:16:09 +02:00
|
|
|
|
2018-10-09 16:37:23 +02:00
|
|
|
params[n_params++] = get_param(s, &ss, &chmap, &b);
|
2018-06-26 15:04:08 +02:00
|
|
|
if (ss.rate > sample_rate) {
|
|
|
|
|
sample_rate = ss.rate;
|
|
|
|
|
stride = pa_frame_size(&ss);
|
|
|
|
|
}
|
2018-06-07 11:16:09 +02:00
|
|
|
}
|
|
|
|
|
}
|
2018-06-26 15:04:08 +02:00
|
|
|
if (sample_rate == 0) {
|
|
|
|
|
sample_rate = 48000;
|
|
|
|
|
stride = sizeof(int16_t) * 2;
|
|
|
|
|
}
|
2018-06-01 11:28:31 +02:00
|
|
|
|
2018-06-05 20:10:31 +02:00
|
|
|
if (attr)
|
|
|
|
|
s->buffer_attr = *attr;
|
2018-06-07 11:16:09 +02:00
|
|
|
patch_buffer_attr(s, &s->buffer_attr, &flags);
|
2018-06-05 20:10:31 +02:00
|
|
|
|
2018-12-05 16:02:03 +01:00
|
|
|
if (direction == PA_STREAM_RECORD)
|
|
|
|
|
devid = s->direct_on_input;
|
|
|
|
|
else
|
|
|
|
|
devid = SPA_ID_INVALID;
|
|
|
|
|
|
2018-11-02 12:31:43 +01:00
|
|
|
if (dev == NULL) {
|
|
|
|
|
if ((str = getenv("PIPEWIRE_NODE")) != NULL)
|
|
|
|
|
devid = atoi(str);
|
|
|
|
|
}
|
2018-12-11 16:37:30 +01:00
|
|
|
else if (devid == SPA_ID_INVALID) {
|
2018-12-04 11:48:15 +01:00
|
|
|
uint32_t mask;
|
|
|
|
|
|
|
|
|
|
if (direction == PA_STREAM_PLAYBACK)
|
|
|
|
|
mask = PA_SUBSCRIPTION_MASK_SINK;
|
|
|
|
|
else if (direction == PA_STREAM_RECORD)
|
|
|
|
|
mask = PA_SUBSCRIPTION_MASK_SOURCE;
|
|
|
|
|
else
|
|
|
|
|
mask = 0;
|
|
|
|
|
|
|
|
|
|
if ((g = pa_context_find_global_by_name(s->context, mask, dev)) != NULL)
|
|
|
|
|
devid = g->id;
|
2018-09-21 16:46:51 +02:00
|
|
|
}
|
2018-06-05 20:10:31 +02:00
|
|
|
|
2018-07-04 18:43:45 +02:00
|
|
|
if ((str = pa_proplist_gets(s->proplist, PA_PROP_MEDIA_ROLE)) == NULL)
|
|
|
|
|
str = "Music";
|
|
|
|
|
else if (strcmp(str, "video") == 0)
|
|
|
|
|
str = "Movie";
|
|
|
|
|
else if (strcmp(str, "music") == 0)
|
|
|
|
|
str = "Music";
|
|
|
|
|
else if (strcmp(str, "game") == 0)
|
|
|
|
|
str = "Game";
|
|
|
|
|
else if (strcmp(str, "event") == 0)
|
|
|
|
|
str = "Notification";
|
|
|
|
|
else if (strcmp(str, "phone") == 0)
|
|
|
|
|
str = "Communication";
|
|
|
|
|
else if (strcmp(str, "animation") == 0)
|
|
|
|
|
str = "Movie";
|
|
|
|
|
else if (strcmp(str, "production") == 0)
|
|
|
|
|
str = "Production";
|
|
|
|
|
else if (strcmp(str, "a11y") == 0)
|
|
|
|
|
str = "Accessibility";
|
|
|
|
|
else if (strcmp(str, "test") == 0)
|
|
|
|
|
str = "Test";
|
|
|
|
|
else
|
|
|
|
|
str = "Music";
|
2018-10-23 16:50:17 +02:00
|
|
|
|
2019-04-17 15:25:05 +02:00
|
|
|
sprintf(latency, "%u/%u", s->buffer_attr.minreq / stride, sample_rate);
|
2019-05-24 15:47:05 +02:00
|
|
|
items[0] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_LATENCY, latency);
|
|
|
|
|
items[1] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_TYPE, "Audio");
|
|
|
|
|
items[2] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_CATEGORY,
|
2018-10-23 16:50:17 +02:00
|
|
|
direction == PA_STREAM_PLAYBACK ?
|
|
|
|
|
"Playback" : "Capture");
|
2019-05-24 15:47:05 +02:00
|
|
|
items[3] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_ROLE, str);
|
|
|
|
|
items[4] = SPA_DICT_ITEM_INIT(PW_KEY_STREAM_MONITOR, monitor ? "1" : "0");
|
2018-10-23 16:50:17 +02:00
|
|
|
|
2018-12-05 16:02:03 +01:00
|
|
|
pw_stream_update_properties(s->stream, &SPA_DICT_INIT(items, 5));
|
2018-06-07 11:16:09 +02:00
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
res = pw_stream_connect(s->stream,
|
|
|
|
|
direction == PA_STREAM_PLAYBACK ?
|
|
|
|
|
PW_DIRECTION_OUTPUT :
|
|
|
|
|
PW_DIRECTION_INPUT,
|
2018-11-02 12:31:43 +01:00
|
|
|
devid,
|
2018-06-01 11:28:31 +02:00
|
|
|
fl,
|
2018-06-07 11:16:09 +02:00
|
|
|
params, n_params);
|
2018-06-01 11:28:31 +02:00
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
int pa_stream_connect_playback(
|
|
|
|
|
pa_stream *s,
|
|
|
|
|
const char *dev,
|
|
|
|
|
const pa_buffer_attr *attr,
|
|
|
|
|
pa_stream_flags_t flags,
|
|
|
|
|
const pa_cvolume *volume,
|
|
|
|
|
pa_stream *sync_stream)
|
|
|
|
|
{
|
|
|
|
|
return create_stream(PA_STREAM_PLAYBACK, s, dev, attr, flags, volume, sync_stream);
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
int pa_stream_connect_record(
|
|
|
|
|
pa_stream *s,
|
|
|
|
|
const char *dev,
|
|
|
|
|
const pa_buffer_attr *attr,
|
|
|
|
|
pa_stream_flags_t flags)
|
|
|
|
|
{
|
|
|
|
|
return create_stream(PA_STREAM_RECORD, s, dev, attr, flags, NULL, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-05 20:10:31 +02:00
|
|
|
static void on_disconnected(pa_operation *o, void *userdata)
|
|
|
|
|
{
|
2018-12-14 16:41:19 +01:00
|
|
|
pa_stream_set_state(o->stream, PA_STREAM_TERMINATED);
|
2018-06-05 20:10:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
int pa_stream_disconnect(pa_stream *s)
|
|
|
|
|
{
|
2018-06-05 20:10:31 +02:00
|
|
|
pa_operation *o;
|
2018-12-17 13:27:27 +01:00
|
|
|
pa_context *c = s->context;
|
2018-06-05 20:10:31 +02:00
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
2018-12-17 13:27:27 +01:00
|
|
|
PA_CHECK_VALIDITY(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
|
2018-06-01 11:28:31 +02:00
|
|
|
|
2018-12-14 16:41:19 +01:00
|
|
|
pw_log_debug("stream %p: disconnect", s);
|
2018-12-17 13:27:27 +01:00
|
|
|
pa_stream_ref(s);
|
2018-12-14 16:41:19 +01:00
|
|
|
|
2018-06-07 11:16:09 +02:00
|
|
|
s->disconnecting = true;
|
2018-06-05 20:10:31 +02:00
|
|
|
pw_stream_disconnect(s->stream);
|
2018-12-17 13:27:27 +01:00
|
|
|
|
|
|
|
|
o = pa_operation_new(c, s, on_disconnected, 0);
|
2018-08-02 10:31:29 +02:00
|
|
|
pa_operation_sync(o);
|
2018-06-05 20:10:31 +02:00
|
|
|
pa_operation_unref(o);
|
2018-12-17 13:27:27 +01:00
|
|
|
pa_stream_unref(s);
|
2018-06-07 11:16:09 +02:00
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int peek_buffer(pa_stream *s)
|
|
|
|
|
{
|
|
|
|
|
int32_t avail;
|
|
|
|
|
uint32_t index;
|
|
|
|
|
|
|
|
|
|
if (s->buffer != NULL)
|
|
|
|
|
return 0;
|
|
|
|
|
|
2018-07-03 22:03:25 +02:00
|
|
|
if ((avail = spa_ringbuffer_get_read_index(&s->dequeued_ring, &index)) < MIN_QUEUED)
|
2018-06-07 11:16:09 +02:00
|
|
|
return -EPIPE;
|
|
|
|
|
|
|
|
|
|
s->buffer = s->dequeued[index & MASK_BUFFERS];
|
|
|
|
|
s->buffer_index = index;
|
|
|
|
|
s->buffer_data = s->buffer->buffer->datas[0].data;
|
|
|
|
|
if (s->direction == PA_STREAM_RECORD) {
|
|
|
|
|
s->buffer_size = s->buffer->buffer->datas[0].chunk->size;
|
|
|
|
|
s->buffer_offset = s->buffer->buffer->datas[0].chunk->offset;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
s->buffer_size = s->buffer->buffer->datas[0].maxsize;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int queue_buffer(pa_stream *s)
|
|
|
|
|
{
|
|
|
|
|
if (s->buffer == NULL)
|
|
|
|
|
return 0;
|
|
|
|
|
|
2018-06-22 17:41:12 +02:00
|
|
|
if (s->direction == PA_STREAM_PLAYBACK)
|
|
|
|
|
s->dequeued_size -= s->buffer->buffer->datas[0].maxsize;
|
|
|
|
|
else
|
|
|
|
|
s->dequeued_size -= s->buffer->buffer->datas[0].chunk->size;
|
2018-07-03 22:03:25 +02:00
|
|
|
spa_ringbuffer_read_update(&s->dequeued_ring, s->buffer_index + 1);
|
2018-06-22 17:41:12 +02:00
|
|
|
|
2018-07-19 13:35:46 +02:00
|
|
|
s->buffer->size = s->buffer->buffer->datas[0].chunk->size;
|
2019-01-07 17:54:31 +01:00
|
|
|
pw_log_trace("%p %"PRIu64"/%d", s->buffer, s->buffer->size,
|
2018-07-31 21:38:01 +02:00
|
|
|
s->buffer->buffer->datas[0].chunk->offset);
|
2018-07-19 13:35:46 +02:00
|
|
|
|
2018-06-07 11:16:09 +02:00
|
|
|
pw_stream_queue_buffer(s->stream, s->buffer);
|
|
|
|
|
s->buffer = NULL;
|
2018-10-16 09:58:14 +02:00
|
|
|
s->buffer_offset = 0;
|
2018-06-01 11:28:31 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
int pa_stream_begin_write(
|
|
|
|
|
pa_stream *s,
|
|
|
|
|
void **data,
|
|
|
|
|
size_t *nbytes)
|
|
|
|
|
{
|
2018-06-22 17:41:12 +02:00
|
|
|
int res;
|
2018-06-05 20:10:31 +02:00
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK ||
|
|
|
|
|
s->direction == PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, data, PA_ERR_INVALID);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, nbytes && *nbytes != 0, PA_ERR_INVALID);
|
|
|
|
|
|
2018-06-22 17:41:12 +02:00
|
|
|
if ((res = peek_buffer(s)) < 0) {
|
2018-06-07 11:16:09 +02:00
|
|
|
*data = NULL;
|
|
|
|
|
*nbytes = 0;
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
2018-07-19 13:35:46 +02:00
|
|
|
else {
|
|
|
|
|
size_t max = s->buffer_size - s->buffer_offset;
|
|
|
|
|
*data = SPA_MEMBER(s->buffer_data, s->buffer_offset, void);
|
2019-01-07 15:04:34 +01:00
|
|
|
*nbytes = *nbytes != (size_t)-1 ? SPA_MIN(*nbytes, max) : max;
|
2018-07-19 13:35:46 +02:00
|
|
|
}
|
2018-07-31 21:38:01 +02:00
|
|
|
pw_log_trace("peek buffer %p %zd", *data, *nbytes);
|
2018-06-05 20:10:31 +02:00
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
int pa_stream_cancel_write(pa_stream *s)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK ||
|
|
|
|
|
s->direction == PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
2018-06-05 20:10:31 +02:00
|
|
|
|
2018-07-19 13:35:46 +02:00
|
|
|
pw_log_debug("cancel %p %p %d", s->buffer, s->buffer_data, s->buffer_size);
|
2018-06-05 20:10:31 +02:00
|
|
|
s->buffer = NULL;
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
int pa_stream_write(pa_stream *s,
|
|
|
|
|
const void *data,
|
|
|
|
|
size_t nbytes,
|
|
|
|
|
pa_free_cb_t free_cb,
|
|
|
|
|
int64_t offset,
|
|
|
|
|
pa_seek_mode_t seek)
|
|
|
|
|
{
|
|
|
|
|
return pa_stream_write_ext_free(s, data, nbytes, free_cb, (void*) data, offset, seek);
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
int pa_stream_write_ext_free(pa_stream *s,
|
|
|
|
|
const void *data,
|
|
|
|
|
size_t nbytes,
|
|
|
|
|
pa_free_cb_t free_cb,
|
|
|
|
|
void *free_cb_data,
|
|
|
|
|
int64_t offset,
|
|
|
|
|
pa_seek_mode_t seek)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
spa_assert(data);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK ||
|
|
|
|
|
s->direction == PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, seek <= PA_SEEK_RELATIVE_END, PA_ERR_INVALID);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK ||
|
|
|
|
|
(seek == PA_SEEK_RELATIVE && offset == 0), PA_ERR_INVALID);
|
2018-06-07 11:16:09 +02:00
|
|
|
PA_CHECK_VALIDITY(s->context,
|
|
|
|
|
!s->buffer ||
|
|
|
|
|
((data >= s->buffer_data) &&
|
|
|
|
|
((const char*) data + nbytes <= (const char*) s->buffer_data + s->buffer_size)),
|
|
|
|
|
PA_ERR_INVALID);
|
2018-06-01 11:28:31 +02:00
|
|
|
PA_CHECK_VALIDITY(s->context, offset % pa_frame_size(&s->sample_spec) == 0, PA_ERR_INVALID);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, nbytes % pa_frame_size(&s->sample_spec) == 0, PA_ERR_INVALID);
|
2018-06-07 11:16:09 +02:00
|
|
|
PA_CHECK_VALIDITY(s->context, !free_cb || !s->buffer, PA_ERR_INVALID);
|
2018-06-01 11:28:31 +02:00
|
|
|
|
|
|
|
|
if (s->buffer == NULL) {
|
2018-07-19 13:35:46 +02:00
|
|
|
void *dst;
|
|
|
|
|
const void *src = data;
|
|
|
|
|
size_t towrite = nbytes, dsize;
|
2018-06-22 17:41:12 +02:00
|
|
|
|
2018-07-19 13:35:46 +02:00
|
|
|
while (towrite > 0) {
|
|
|
|
|
dsize = towrite;
|
|
|
|
|
|
|
|
|
|
if (pa_stream_begin_write(s, &dst, &dsize) < 0 ||
|
|
|
|
|
dst == NULL || dsize == 0) {
|
2018-10-16 09:58:14 +02:00
|
|
|
pw_log_debug("stream %p: out of buffers, wanted %zd bytes", s, nbytes);
|
2018-07-19 13:35:46 +02:00
|
|
|
break;
|
|
|
|
|
}
|
2018-06-22 17:41:12 +02:00
|
|
|
|
2018-07-19 13:35:46 +02:00
|
|
|
memcpy(dst, src, dsize);
|
|
|
|
|
|
2018-10-16 09:58:14 +02:00
|
|
|
s->buffer_offset += dsize;
|
2018-07-19 13:35:46 +02:00
|
|
|
|
2018-10-16 09:58:14 +02:00
|
|
|
if (s->buffer_offset >= s->buffer_size) {
|
|
|
|
|
s->buffer->buffer->datas[0].chunk->offset = 0;
|
|
|
|
|
s->buffer->buffer->datas[0].chunk->size = s->buffer_offset;
|
|
|
|
|
queue_buffer(s);
|
|
|
|
|
}
|
2018-07-19 13:35:46 +02:00
|
|
|
towrite -= dsize;
|
2019-01-08 11:55:42 +01:00
|
|
|
src = SPA_MEMBER(src, dsize, void);
|
2018-07-19 13:35:46 +02:00
|
|
|
}
|
|
|
|
|
if (free_cb)
|
|
|
|
|
free_cb(free_cb_data);
|
2018-10-16 09:58:14 +02:00
|
|
|
|
|
|
|
|
s->buffer = NULL;
|
2018-06-26 16:57:49 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2019-01-08 11:55:42 +01:00
|
|
|
s->buffer->buffer->datas[0].chunk->offset = SPA_PTRDIFF(data, s->buffer_data);
|
2018-06-26 16:57:49 +02:00
|
|
|
s->buffer->buffer->datas[0].chunk->size = nbytes;
|
|
|
|
|
queue_buffer(s);
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
2018-06-22 17:41:12 +02:00
|
|
|
|
2018-07-19 13:35:46 +02:00
|
|
|
update_timing_info(s);
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
int pa_stream_peek(pa_stream *s,
|
|
|
|
|
const void **data,
|
|
|
|
|
size_t *nbytes)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
spa_assert(data);
|
|
|
|
|
spa_assert(nbytes);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE);
|
|
|
|
|
|
2018-06-07 11:16:09 +02:00
|
|
|
if (peek_buffer(s) < 0) {
|
|
|
|
|
*data = NULL;
|
|
|
|
|
*nbytes = 0;
|
2018-06-22 17:41:12 +02:00
|
|
|
pw_log_debug("stream %p: no buffer", s);
|
2018-06-07 11:16:09 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
*data = SPA_MEMBER(s->buffer_data, s->buffer_offset, void);
|
2018-10-23 16:50:17 +02:00
|
|
|
*nbytes = s->buffer_size;
|
2018-12-11 16:37:30 +01:00
|
|
|
pw_log_trace("stream %p: %p %zd %f", s, *data, *nbytes, *(float*)*data);
|
2018-06-07 11:16:09 +02:00
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
int pa_stream_drop(pa_stream *s)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE);
|
2018-06-07 11:16:09 +02:00
|
|
|
PA_CHECK_VALIDITY(s->context, s->buffer, PA_ERR_BADSTATE);
|
|
|
|
|
|
2018-07-31 21:38:01 +02:00
|
|
|
pw_log_trace("stream %p", s);
|
2018-06-07 11:16:09 +02:00
|
|
|
queue_buffer(s);
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2019-09-27 07:45:15 +05:30
|
|
|
size_t pa_stream_writable_size(PA_CONST pa_stream *s)
|
2018-06-01 11:28:31 +02:00
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY,
|
|
|
|
|
PA_ERR_BADSTATE, (size_t) -1);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction != PA_STREAM_RECORD,
|
|
|
|
|
PA_ERR_BADSTATE, (size_t) -1);
|
|
|
|
|
|
2018-10-16 09:58:14 +02:00
|
|
|
pw_log_trace("stream %p: %zd", s, s->dequeued_size);
|
2018-06-05 20:10:31 +02:00
|
|
|
return s->dequeued_size;
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2019-09-27 07:45:15 +05:30
|
|
|
size_t pa_stream_readable_size(PA_CONST pa_stream *s)
|
2018-06-01 11:28:31 +02:00
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY,
|
|
|
|
|
PA_ERR_BADSTATE, (size_t) -1);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction == PA_STREAM_RECORD,
|
|
|
|
|
PA_ERR_BADSTATE, (size_t) -1);
|
2018-06-07 11:16:09 +02:00
|
|
|
|
|
|
|
|
return s->dequeued_size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct success_ack {
|
|
|
|
|
pa_stream_success_cb_t cb;
|
|
|
|
|
void *userdata;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void on_success(pa_operation *o, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
struct success_ack *d = userdata;
|
2018-06-22 17:41:12 +02:00
|
|
|
pa_stream *s = o->stream;
|
2018-06-07 11:16:09 +02:00
|
|
|
pa_operation_done(o);
|
|
|
|
|
if (d->cb)
|
2018-08-02 10:31:29 +02:00
|
|
|
d->cb(s, 1, d->userdata);
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
pa_operation* pa_stream_drain(pa_stream *s, pa_stream_success_cb_t cb, void *userdata)
|
|
|
|
|
{
|
2018-06-07 11:16:09 +02:00
|
|
|
pa_operation *o;
|
|
|
|
|
struct success_ack *d;
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE);
|
|
|
|
|
|
2018-08-02 10:31:29 +02:00
|
|
|
pw_log_debug("stream %p", s);
|
2019-08-16 15:23:05 +02:00
|
|
|
pw_stream_flush(s->stream, true);
|
2018-06-07 11:16:09 +02:00
|
|
|
o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack));
|
|
|
|
|
d = o->userdata;
|
|
|
|
|
d->cb = cb;
|
|
|
|
|
d->userdata = userdata;
|
2018-08-02 10:31:29 +02:00
|
|
|
if (s->drain)
|
|
|
|
|
pa_operation_cancel(s->drain);
|
|
|
|
|
s->drain = o;
|
2018-06-07 11:16:09 +02:00
|
|
|
|
|
|
|
|
return o;
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
2018-06-22 17:41:12 +02:00
|
|
|
static void on_timing_success(pa_operation *o, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
struct success_ack *d = userdata;
|
|
|
|
|
pa_stream *s = o->stream;
|
2018-07-19 13:35:46 +02:00
|
|
|
|
|
|
|
|
update_timing_info(s);
|
2018-06-22 17:41:12 +02:00
|
|
|
pa_operation_done(o);
|
|
|
|
|
|
|
|
|
|
if (d->cb)
|
|
|
|
|
d->cb(s, s->timing_info_valid, d->userdata);
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
pa_operation* pa_stream_update_timing_info(pa_stream *s, pa_stream_success_cb_t cb, void *userdata)
|
|
|
|
|
{
|
2018-06-07 11:16:09 +02:00
|
|
|
pa_operation *o;
|
|
|
|
|
struct success_ack *d;
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
|
|
|
|
|
2018-06-22 17:41:12 +02:00
|
|
|
o = pa_operation_new(s->context, s, on_timing_success, sizeof(struct success_ack));
|
2018-06-07 11:16:09 +02:00
|
|
|
d = o->userdata;
|
|
|
|
|
d->cb = cb;
|
|
|
|
|
d->userdata = userdata;
|
2018-08-02 10:31:29 +02:00
|
|
|
pa_operation_sync(o);
|
2018-06-07 11:16:09 +02:00
|
|
|
|
|
|
|
|
return o;
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
void pa_stream_set_state_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
s->state_callback = cb;
|
|
|
|
|
s->state_userdata = userdata;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
void pa_stream_set_write_callback(pa_stream *s, pa_stream_request_cb_t cb, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
s->write_callback = cb;
|
|
|
|
|
s->write_userdata = userdata;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
void pa_stream_set_read_callback(pa_stream *s, pa_stream_request_cb_t cb, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
s->read_callback = cb;
|
|
|
|
|
s->read_userdata = userdata;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
void pa_stream_set_overflow_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
s->overflow_callback = cb;
|
|
|
|
|
s->overflow_userdata = userdata;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2019-09-27 07:45:15 +05:30
|
|
|
int64_t pa_stream_get_underflow_index(PA_CONST pa_stream *s)
|
2018-06-01 11:28:31 +02:00
|
|
|
{
|
|
|
|
|
pw_log_warn("Not Implemented");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
void pa_stream_set_underflow_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
s->underflow_callback = cb;
|
|
|
|
|
s->underflow_userdata = userdata;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
void pa_stream_set_started_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
s->started_callback = cb;
|
|
|
|
|
s->started_userdata = userdata;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
void pa_stream_set_latency_update_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
s->latency_update_callback = cb;
|
|
|
|
|
s->latency_update_userdata = userdata;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
void pa_stream_set_moved_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
s->moved_callback = cb;
|
|
|
|
|
s->moved_userdata = userdata;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
void pa_stream_set_suspended_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
s->suspended_callback = cb;
|
|
|
|
|
s->suspended_userdata = userdata;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
void pa_stream_set_event_callback(pa_stream *s, pa_stream_event_cb_t cb, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
s->event_callback = cb;
|
|
|
|
|
s->event_userdata = userdata;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
void pa_stream_set_buffer_attr_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
if (s->state == PA_STREAM_TERMINATED || s->state == PA_STREAM_FAILED)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
s->buffer_attr_callback = cb;
|
|
|
|
|
s->buffer_attr_userdata = userdata;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
pa_operation* pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, void *userdata)
|
|
|
|
|
{
|
2018-06-05 20:10:31 +02:00
|
|
|
pa_operation *o;
|
|
|
|
|
struct success_ack *d;
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
|
|
|
|
|
|
|
|
|
s->corked = b;
|
2018-06-05 20:10:31 +02:00
|
|
|
|
2018-10-09 16:39:19 +02:00
|
|
|
pw_stream_set_active(s->stream, !b);
|
2018-06-05 20:10:31 +02:00
|
|
|
o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack));
|
|
|
|
|
d = o->userdata;
|
|
|
|
|
d->cb = cb;
|
|
|
|
|
d->userdata = userdata;
|
2018-08-02 10:31:29 +02:00
|
|
|
pa_operation_sync(o);
|
2018-06-01 11:28:31 +02:00
|
|
|
|
2018-06-05 20:10:31 +02:00
|
|
|
return o;
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
pa_operation* pa_stream_flush(pa_stream *s, pa_stream_success_cb_t cb, void *userdata)
|
|
|
|
|
{
|
2018-06-05 20:10:31 +02:00
|
|
|
pa_operation *o;
|
|
|
|
|
struct success_ack *d;
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
|
|
|
|
|
2018-10-09 16:39:19 +02:00
|
|
|
pw_stream_flush(s->stream, false);
|
|
|
|
|
update_timing_info(s);
|
2018-06-05 20:10:31 +02:00
|
|
|
o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack));
|
|
|
|
|
d = o->userdata;
|
|
|
|
|
d->cb = cb;
|
|
|
|
|
d->userdata = userdata;
|
2018-08-02 10:31:29 +02:00
|
|
|
pa_operation_sync(o);
|
2018-06-05 20:10:31 +02:00
|
|
|
|
|
|
|
|
return o;
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
pa_operation* pa_stream_prebuf(pa_stream *s, pa_stream_success_cb_t cb, void *userdata)
|
|
|
|
|
{
|
2018-06-05 20:10:31 +02:00
|
|
|
pa_operation *o;
|
|
|
|
|
struct success_ack *d;
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->buffer_attr.prebuf > 0, PA_ERR_BADSTATE);
|
|
|
|
|
|
2018-06-05 20:10:31 +02:00
|
|
|
pw_log_warn("Not Implemented");
|
|
|
|
|
o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack));
|
|
|
|
|
d = o->userdata;
|
|
|
|
|
d->cb = cb;
|
|
|
|
|
d->userdata = userdata;
|
2018-08-02 10:31:29 +02:00
|
|
|
pa_operation_sync(o);
|
2018-06-05 20:10:31 +02:00
|
|
|
|
|
|
|
|
return o;
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
pa_operation* pa_stream_trigger(pa_stream *s, pa_stream_success_cb_t cb, void *userdata)
|
|
|
|
|
{
|
2018-06-05 20:10:31 +02:00
|
|
|
pa_operation *o;
|
|
|
|
|
struct success_ack *d;
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->buffer_attr.prebuf > 0, PA_ERR_BADSTATE);
|
|
|
|
|
|
2018-06-05 20:10:31 +02:00
|
|
|
pw_log_warn("Not Implemented");
|
|
|
|
|
o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack));
|
|
|
|
|
d = o->userdata;
|
|
|
|
|
d->cb = cb;
|
|
|
|
|
d->userdata = userdata;
|
2018-08-02 10:31:29 +02:00
|
|
|
pa_operation_sync(o);
|
2018-06-05 20:10:31 +02:00
|
|
|
|
|
|
|
|
return o;
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
pa_operation* pa_stream_set_name(pa_stream *s, const char *name, pa_stream_success_cb_t cb, void *userdata)
|
|
|
|
|
{
|
2018-06-05 20:10:31 +02:00
|
|
|
pa_operation *o;
|
|
|
|
|
struct success_ack *d;
|
2018-10-16 09:56:12 +02:00
|
|
|
struct spa_dict dict;
|
|
|
|
|
struct spa_dict_item items[1];
|
2018-06-05 20:10:31 +02:00
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
spa_assert(name);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
|
|
|
|
|
2019-05-24 15:47:05 +02:00
|
|
|
items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_NAME, name);
|
2018-10-16 09:56:12 +02:00
|
|
|
dict = SPA_DICT_INIT(items, 1);
|
|
|
|
|
pw_stream_update_properties(s->stream, &dict);
|
|
|
|
|
|
2018-06-05 20:10:31 +02:00
|
|
|
o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack));
|
|
|
|
|
d = o->userdata;
|
|
|
|
|
d->cb = cb;
|
|
|
|
|
d->userdata = userdata;
|
2018-08-02 10:31:29 +02:00
|
|
|
pa_operation_sync(o);
|
2018-06-05 20:10:31 +02:00
|
|
|
|
|
|
|
|
return o;
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
int pa_stream_get_time(pa_stream *s, pa_usec_t *r_usec)
|
|
|
|
|
{
|
2018-06-07 11:16:09 +02:00
|
|
|
pa_usec_t res;
|
2018-06-22 17:41:12 +02:00
|
|
|
struct timespec ts;
|
2018-07-19 13:35:46 +02:00
|
|
|
uint64_t now, delay, read_time;
|
|
|
|
|
pa_timing_info *i;
|
2018-06-07 11:16:09 +02:00
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->timing_info_valid, PA_ERR_NODATA);
|
|
|
|
|
|
2018-06-22 17:41:12 +02:00
|
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
2018-11-07 09:57:22 +01:00
|
|
|
now = SPA_TIMESPEC_TO_USEC(&ts);
|
2018-06-22 17:41:12 +02:00
|
|
|
|
2018-07-19 13:35:46 +02:00
|
|
|
i = &s->timing_info;
|
2018-11-07 09:57:22 +01:00
|
|
|
delay = now - SPA_TIMEVAL_TO_USEC(&i->timestamp);
|
2018-07-19 13:35:46 +02:00
|
|
|
read_time = pa_bytes_to_usec((uint64_t) i->read_index, &s->sample_spec);
|
|
|
|
|
|
|
|
|
|
res = delay + read_time;
|
2018-06-07 11:16:09 +02:00
|
|
|
|
|
|
|
|
if (r_usec)
|
|
|
|
|
*r_usec = res;
|
2018-06-05 20:10:31 +02:00
|
|
|
|
2019-06-07 17:03:49 +02:00
|
|
|
pw_log_trace("stream %p: %"PRIu64" %"PRIu64" %"PRIu64" %"PRIi64" %"PRIi64" %"PRIi64" %"PRIu64,
|
|
|
|
|
s, now, delay, read_time,
|
2018-07-19 13:35:46 +02:00
|
|
|
i->write_index, i->read_index,
|
|
|
|
|
i->write_index - i->read_index,
|
|
|
|
|
res);
|
2018-06-22 17:41:12 +02:00
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-19 13:35:46 +02:00
|
|
|
static pa_usec_t time_counter_diff(const pa_stream *s, pa_usec_t a, pa_usec_t b, int *negative) {
|
|
|
|
|
pa_assert(s);
|
|
|
|
|
pa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
if (negative)
|
|
|
|
|
*negative = 0;
|
|
|
|
|
|
|
|
|
|
if (a >= b)
|
|
|
|
|
return a-b;
|
|
|
|
|
else {
|
|
|
|
|
if (negative && s->direction == PA_STREAM_RECORD) {
|
|
|
|
|
*negative = 1;
|
|
|
|
|
return b-a;
|
|
|
|
|
} else
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
int pa_stream_get_latency(pa_stream *s, pa_usec_t *r_usec, int *negative)
|
|
|
|
|
{
|
2018-07-19 13:35:46 +02:00
|
|
|
pa_usec_t t, c;
|
|
|
|
|
int64_t cindex;
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
spa_assert(r_usec);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->timing_info_valid, PA_ERR_NODATA);
|
|
|
|
|
|
2018-07-19 13:35:46 +02:00
|
|
|
pa_stream_get_time(s, &t);
|
|
|
|
|
|
|
|
|
|
if (s->direction == PA_STREAM_PLAYBACK)
|
|
|
|
|
cindex = s->timing_info.write_index;
|
|
|
|
|
else
|
|
|
|
|
cindex = s->timing_info.read_index;
|
|
|
|
|
|
|
|
|
|
if (cindex < 0)
|
|
|
|
|
cindex = 0;
|
|
|
|
|
|
|
|
|
|
c = pa_bytes_to_usec((uint64_t) cindex, &s->sample_spec);
|
|
|
|
|
|
|
|
|
|
if (s->direction == PA_STREAM_PLAYBACK)
|
|
|
|
|
*r_usec = time_counter_diff(s, c, t, negative);
|
|
|
|
|
else
|
|
|
|
|
*r_usec = time_counter_diff(s, t, c, negative);
|
2018-06-05 20:10:31 +02:00
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
const pa_timing_info* pa_stream_get_timing_info(pa_stream *s)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->timing_info_valid, PA_ERR_NODATA);
|
|
|
|
|
|
2019-06-07 17:03:49 +02:00
|
|
|
pw_log_trace("stream %p: %"PRIi64" %"PRIi64" %"PRIi64, s,
|
2018-07-19 13:35:46 +02:00
|
|
|
s->timing_info.write_index, s->timing_info.read_index,
|
|
|
|
|
(s->timing_info.write_index - s->timing_info.read_index));
|
2018-06-22 17:41:12 +02:00
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
return &s->timing_info;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
const pa_sample_spec* pa_stream_get_sample_spec(pa_stream *s)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
return &s->sample_spec;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
const pa_channel_map* pa_stream_get_channel_map(pa_stream *s)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
return &s->channel_map;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2019-09-27 07:45:15 +05:30
|
|
|
const pa_format_info* pa_stream_get_format_info(PA_CONST pa_stream *s)
|
2018-06-01 11:28:31 +02:00
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
|
|
|
|
|
return s->format;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
const pa_buffer_attr* pa_stream_get_buffer_attr(pa_stream *s)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
|
|
|
|
|
|
|
|
|
return &s->buffer_attr;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
pa_operation *pa_stream_set_buffer_attr(pa_stream *s, const pa_buffer_attr *attr, pa_stream_success_cb_t cb, void *userdata)
|
|
|
|
|
{
|
2018-06-07 11:16:09 +02:00
|
|
|
pa_operation *o;
|
|
|
|
|
struct success_ack *d;
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
spa_assert(attr);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
|
|
|
|
|
2018-06-07 11:16:09 +02:00
|
|
|
pw_log_warn("Not Implemented");
|
|
|
|
|
o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack));
|
|
|
|
|
d = o->userdata;
|
|
|
|
|
d->cb = cb;
|
|
|
|
|
d->userdata = userdata;
|
2018-08-02 10:31:29 +02:00
|
|
|
pa_operation_sync(o);
|
2018-06-07 11:16:09 +02:00
|
|
|
return o;
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
pa_operation *pa_stream_update_sample_rate(pa_stream *s, uint32_t rate, pa_stream_success_cb_t cb, void *userdata)
|
|
|
|
|
{
|
2018-06-07 11:16:09 +02:00
|
|
|
pa_operation *o;
|
|
|
|
|
struct success_ack *d;
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, pa_sample_rate_valid(rate), PA_ERR_INVALID);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->flags & PA_STREAM_VARIABLE_RATE, PA_ERR_BADSTATE);
|
|
|
|
|
|
2018-06-07 11:16:09 +02:00
|
|
|
pw_log_warn("Not Implemented");
|
|
|
|
|
o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack));
|
|
|
|
|
d = o->userdata;
|
|
|
|
|
d->cb = cb;
|
|
|
|
|
d->userdata = userdata;
|
2018-08-02 10:31:29 +02:00
|
|
|
pa_operation_sync(o);
|
2018-06-07 11:16:09 +02:00
|
|
|
return o;
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
pa_operation *pa_stream_proplist_update(pa_stream *s, pa_update_mode_t mode, pa_proplist *p, pa_stream_success_cb_t cb, void *userdata)
|
|
|
|
|
{
|
2018-06-07 11:16:09 +02:00
|
|
|
pa_operation *o;
|
|
|
|
|
struct success_ack *d;
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, mode == PA_UPDATE_SET ||
|
|
|
|
|
mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE, PA_ERR_INVALID);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
|
|
|
|
|
2019-01-04 10:00:57 +01:00
|
|
|
pa_proplist_update(s->proplist, mode, p);
|
2018-10-23 16:50:17 +02:00
|
|
|
|
2018-06-07 11:16:09 +02:00
|
|
|
o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack));
|
|
|
|
|
d = o->userdata;
|
|
|
|
|
d->cb = cb;
|
|
|
|
|
d->userdata = userdata;
|
2018-08-02 10:31:29 +02:00
|
|
|
pa_operation_sync(o);
|
2018-06-07 11:16:09 +02:00
|
|
|
return o;
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
pa_operation *pa_stream_proplist_remove(pa_stream *s, const char *const keys[], pa_stream_success_cb_t cb, void *userdata)
|
|
|
|
|
{
|
2018-06-07 11:16:09 +02:00
|
|
|
pa_operation *o;
|
|
|
|
|
struct success_ack *d;
|
|
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, keys && keys[0], PA_ERR_INVALID);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE);
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE);
|
|
|
|
|
|
2018-06-07 11:16:09 +02:00
|
|
|
pw_log_warn("Not Implemented");
|
|
|
|
|
o = pa_operation_new(s->context, s, on_success, sizeof(struct success_ack));
|
|
|
|
|
d = o->userdata;
|
|
|
|
|
d->cb = cb;
|
|
|
|
|
d->userdata = userdata;
|
2018-08-02 10:31:29 +02:00
|
|
|
pa_operation_sync(o);
|
2018-06-07 11:16:09 +02:00
|
|
|
return o;
|
2018-06-01 11:28:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2018-06-01 11:28:31 +02:00
|
|
|
int pa_stream_set_monitor_stream(pa_stream *s, uint32_t sink_input_idx)
|
|
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
2018-12-05 16:02:03 +01:00
|
|
|
pw_log_warn("stream %p: Not implemented %d", s, sink_input_idx);
|
2018-10-19 13:30:20 +02:00
|
|
|
|
2018-06-01 11:28:31 +02:00
|
|
|
PA_CHECK_VALIDITY(s->context, sink_input_idx != PA_INVALID_INDEX, PA_ERR_INVALID);
|
|
|
|
|
PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_UNCONNECTED, PA_ERR_BADSTATE);
|
|
|
|
|
|
|
|
|
|
s->direct_on_input = sink_input_idx;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-06 13:23:43 +01:00
|
|
|
SPA_EXPORT
|
2019-09-27 07:45:15 +05:30
|
|
|
uint32_t pa_stream_get_monitor_stream(PA_CONST pa_stream *s)
|
2018-06-01 11:28:31 +02:00
|
|
|
{
|
|
|
|
|
spa_assert(s);
|
|
|
|
|
spa_assert(s->refcount >= 1);
|
|
|
|
|
|
|
|
|
|
PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direct_on_input != PA_INVALID_INDEX,
|
|
|
|
|
PA_ERR_BADSTATE, PA_INVALID_INDEX);
|
|
|
|
|
|
|
|
|
|
return s->direct_on_input;
|
|
|
|
|
}
|