[alsa-devel] [RFC - AAF PCM plugin 3/5] aaf: Implement Playback mode support
Pierre-Louis Bossart
pierre-louis.bossart at linux.intel.com
Tue Aug 21 05:37:31 CEST 2018
On 8/20/18 8:06 PM, Andre Guedes wrote:
> This patch implements the playback mode support from the AAF plugin.
> Simply put, this mode works as follows: PCM samples provided by alsa-lib
> layer are encapsulated into AVTP packets and transmitted through the
> network. In summary, the playback mode implements a typical AVTP Talker.
>
> When the AAF device is put in running state, its media clock is started.
> At every tick from the media clock, audio frames are consumed from the
> audio buffer, encapsulated into an AVTP packet, and transmitted to the
> network. The presentation time from each AVTP packet is calculated
> taking in consideration the maximum transit time and time uncertainty
> values configured by the user.
>
> Below follows some discussion about implementation details:
>
> Even though only one file descriptor is used to implement the playback
> mode, this patch doesn't leverage ioplug->poll_fd but defines poll
> callbacks instead. The reason is these callbacks will be required to
> support capture mode (to be implemented by upcoming patch).
>
> The TSN data plane interface is the AF_PACKET socket family so the
> plugin uses an AF_PACKET socket to send/receive AVTP packets. Linux
> requires CAP_NET_RAW capability in order to open an AF_PACKET socket so
> the application that instantiates the plugin must have it. For further
> info about AF_PACKET socket family see packet(7).
>
> Signed-off-by: Andre Guedes <andre.guedes at intel.com>
> ---
> aaf/pcm_aaf.c | 611 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> doc/aaf.txt | 40 ++++
> 2 files changed, 651 insertions(+)
>
> diff --git a/aaf/pcm_aaf.c b/aaf/pcm_aaf.c
> index 4c6f031..72f6652 100644
> --- a/aaf/pcm_aaf.c
> +++ b/aaf/pcm_aaf.c
> @@ -20,13 +20,30 @@
>
> #include <alsa/asoundlib.h>
> #include <alsa/pcm_external.h>
> +#include <arpa/inet.h>
> +#include <avtp.h>
> +#include <avtp_aaf.h>
> +#include <limits.h>
> #include <linux/if.h>
> #include <linux/if_ether.h>
> +#include <linux/if_packet.h>
> #include <string.h>
> #include <stdint.h>
> +#include <sys/ioctl.h>
> +#include <sys/timerfd.h>
>
> +#ifdef AAF_DEBUG
> +#define pr_debug(...) SNDERR(__VA_ARGS__)
> +#else
> +#define pr_debug(...) (void)0
> +#endif
> +
> +#define CLOCK_REF CLOCK_REALTIME
You should probably comment on why you use CLOCK_REALTIME for something
that is driven by a media clock that's completely unrelated to
CLOCK_REALTIME?
> +#define NSEC_PER_SEC 1000000000ULL
> #define NSEC_PER_USEC 1000ULL
>
> +#define FD_COUNT_PLAYBACK 1
> +
> typedef struct {
> snd_pcm_ioplug_t io;
>
> @@ -37,8 +54,74 @@ typedef struct {
> int mtt;
> int t_uncertainty;
> int frames_per_pkt;
> +
> + int sk_fd;
> + int timer_fd;
> +
> + struct sockaddr_ll sk_addr;
> +
> + char *audiobuf;
> +
> + struct avtp_stream_pdu *pdu;
> + int pdu_size;
> + uint8_t pdu_seq;
> +
> + uint64_t mclk_start_time;
> + uint64_t mclk_period;
> + uint64_t mclk_ticks;
> +
> + snd_pcm_channel_area_t *audiobuf_areas;
> + snd_pcm_channel_area_t *payload_areas;
> +
> + snd_pcm_sframes_t hw_ptr;
> + snd_pcm_sframes_t buffer_size;
> + snd_pcm_sframes_t boundary;
> } snd_pcm_aaf_t;
>
> +static unsigned int alsa_to_avtp_format(snd_pcm_format_t format)
> +{
> + switch (format) {
> + case SND_PCM_FORMAT_S16_BE:
we usually use S16_LE? I can't recall when I last used S16_BE...
> + return AVTP_AAF_FORMAT_INT_16BIT;
> + case SND_PCM_FORMAT_S24_3BE:
this means 3 bytes without padding to 32-bit word, is this your
definition as well?
> + return AVTP_AAF_FORMAT_INT_24BIT;
> + case SND_PCM_FORMAT_S32_BE:
> + return AVTP_AAF_FORMAT_INT_32BIT;
> + case SND_PCM_FORMAT_FLOAT_BE:
> + return AVTP_AAF_FORMAT_FLOAT_32BIT;
> + default:
> + return AVTP_AAF_FORMAT_USER;
> + }
> +}
> +
> +static unsigned int alsa_to_avtp_rate(unsigned int rate)
> +{
> + switch (rate) {
> + case 8000:
> + return AVTP_AAF_PCM_NSR_8KHZ;
> + case 16000:
> + return AVTP_AAF_PCM_NSR_16KHZ;
> + case 24000:
> + return AVTP_AAF_PCM_NSR_24KHZ;
> + case 32000:
> + return AVTP_AAF_PCM_NSR_32KHZ;
> + case 44100:
> + return AVTP_AAF_PCM_NSR_44_1KHZ;
> + case 48000:
> + return AVTP_AAF_PCM_NSR_48KHZ;
> + case 88200:
> + return AVTP_AAF_PCM_NSR_88_2KHZ;
> + case 96000:
> + return AVTP_AAF_PCM_NSR_96KHZ;
> + case 176400:
> + return AVTP_AAF_PCM_NSR_176_4KHZ;
> + case 192000:
> + return AVTP_AAF_PCM_NSR_192KHZ;
You should align avtp_aaf definitions with ALSA, you are missing quite a
few frequencies. e.g. 11.025, 64, 384kHz. If this is intentional add a
comment to explain the restrictions.
> + default:
> + return AVTP_AAF_PCM_NSR_USER;
> + }
> +}
> +
> static int aaf_load_config(snd_pcm_aaf_t *aaf, snd_config_t *conf)
> {
> snd_config_iterator_t cur, next;
> @@ -147,6 +230,327 @@ err:
> return -EINVAL;
> }
>
> +static int aaf_init_socket(snd_pcm_aaf_t *aaf)
> +{
> + int fd, res;
> + struct ifreq req;
> +
> + fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_TSN));
> + if (fd < 0) {
> + SNDERR("Failed to open AF_PACKET socket");
> + return -errno;
> + }
> +
> + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", aaf->ifname);
> + res = ioctl(fd, SIOCGIFINDEX, &req);
> + if (res < 0) {
> + SNDERR("Failed to get network interface index");
> + res = -errno;
> + goto err;
> + }
> +
> + aaf->sk_addr.sll_family = AF_PACKET;
> + aaf->sk_addr.sll_protocol = htons(ETH_P_TSN);
> + aaf->sk_addr.sll_halen = ETH_ALEN;
> + aaf->sk_addr.sll_ifindex = req.ifr_ifindex;
> + memcpy(&aaf->sk_addr.sll_addr, aaf->addr, ETH_ALEN);
> +
> + res = setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &aaf->prio,
> + sizeof(aaf->prio));
> + if (res < 0) {
> + SNDERR("Failed to set socket priority");
> + res = -errno;
> + goto err;
> + }
> +
> + aaf->sk_fd = fd;
> + return 0;
> +
> +err:
> + close(fd);
> + return res;
> +}
> +
> +static int aaf_init_timer(snd_pcm_aaf_t *aaf)
> +{
> + int fd;
> +
> + fd = timerfd_create(CLOCK_REF, 0);
> + if (fd < 0)
> + return -errno;
> +
> + aaf->timer_fd = fd;
> + return 0;
> +}
> +
> +static int aaf_init_pdu(snd_pcm_aaf_t *aaf)
> +{
> + int res;
> + struct avtp_stream_pdu *pdu;
> + ssize_t frame_size, payload_size, pdu_size;
> + snd_pcm_ioplug_t *io = &aaf->io;
> +
> + frame_size = snd_pcm_format_size(io->format, io->channels);
> + if (frame_size < 0)
> + return frame_size;
> +
> + payload_size = frame_size * aaf->frames_per_pkt;
> + pdu_size = sizeof(*pdu) + payload_size;
> + pdu = calloc(1, pdu_size);
> + if (!pdu)
> + return -ENOMEM;
> +
> + res = avtp_aaf_pdu_init(pdu);
> + if (res < 0)
> + goto err;
> +
> + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_TV, 1);
> + if (res < 0)
> + goto err;
> +
> + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_STREAM_ID, aaf->streamid);
> + if (res < 0)
> + goto err;
> +
> + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_FORMAT,
> + alsa_to_avtp_format(io->format));
> + if (res < 0)
> + goto err;
> +
> + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_NSR,
> + alsa_to_avtp_rate(io->rate));
> + if (res < 0)
> + goto err;
> +
> + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_CHAN_PER_FRAME,
> + io->channels);
> + if (res < 0)
> + goto err;
> +
> + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_BIT_DEPTH,
> + snd_pcm_format_width(io->format));
> + if (res < 0)
> + goto err;
> +
> + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_STREAM_DATA_LEN,
> + payload_size);
> + if (res < 0)
> + goto err;
> +
> + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_SP, AVTP_AAF_PCM_SP_NORMAL);
> + if (res < 0)
> + goto err;
> +
> + aaf->pdu = pdu;
> + aaf->pdu_size = pdu_size;
> + aaf->pdu_seq = 0;
> + return 0;
> +
> +err:
> + free(pdu);
> + return res;
> +}
> +
> +static int aaf_init_audio_buffer(snd_pcm_aaf_t *aaf)
> +{
> + char *audiobuf;
> + ssize_t frame_size, audiobuf_size;
> + snd_pcm_ioplug_t *io = &aaf->io;
> +
> + frame_size = snd_pcm_format_size(io->format, io->channels);
> + if (frame_size < 0)
> + return frame_size;
> +
> + audiobuf_size = frame_size * aaf->buffer_size;
> + audiobuf = calloc(1, audiobuf_size);
> + if (!audiobuf)
> + return -ENOMEM;
> +
> + aaf->audiobuf = audiobuf;
> + return 0;
> +}
> +
> +static int aaf_init_areas(snd_pcm_aaf_t *aaf)
> +{
> + snd_pcm_channel_area_t *audiobuf_areas, *payload_areas;
> + ssize_t sample_size, frame_size;
> + snd_pcm_ioplug_t *io = &aaf->io;
> +
> + sample_size = snd_pcm_format_size(io->format, 1);
> + if (sample_size < 0)
> + return sample_size;
> +
> + frame_size = sample_size * io->channels;
> +
> + audiobuf_areas = calloc(io->channels, sizeof(snd_pcm_channel_area_t));
> + if (!audiobuf_areas)
> + return -ENOMEM;
> +
> + payload_areas = calloc(io->channels, sizeof(snd_pcm_channel_area_t));
> + if (!payload_areas) {
> + free(audiobuf_areas);
> + return -ENOMEM;
> + }
> +
> + for (unsigned int i = 0; i < io->channels; i++) {
> + audiobuf_areas[i].addr = aaf->audiobuf;
> + audiobuf_areas[i].first = i * sample_size * 8;
> + audiobuf_areas[i].step = frame_size * 8;
> +
> + payload_areas[i].addr = aaf->pdu->avtp_payload;
> + payload_areas[i].first = i * sample_size * 8;
> + payload_areas[i].step = frame_size * 8;
not sure what 8 represents here?
> + }
> +
> + aaf->audiobuf_areas = audiobuf_areas;
> + aaf->payload_areas = payload_areas;
> + return 0;
> +}
> +
> +static void aaf_inc_hw_ptr(snd_pcm_aaf_t *aaf, snd_pcm_sframes_t val)
> +{
> + aaf->hw_ptr += val;
> +
> + if (aaf->hw_ptr >= aaf->boundary)
> + aaf->hw_ptr -= aaf->boundary;
> +}
> +
> +static int aaf_mclk_start_playback(snd_pcm_aaf_t *aaf)
> +{
> + int res;
> + struct timespec now;
> + struct itimerspec itspec;
> + snd_pcm_ioplug_t *io = &aaf->io;
> +
> + res = clock_gettime(CLOCK_REF, &now);
> + if (res < 0) {
> + SNDERR("Failed to get time from clock");
> + return -errno;
> + }
> +
> + aaf->mclk_period = (NSEC_PER_SEC * aaf->frames_per_pkt) / io->rate;
is this always an integer? If not, don't you have a systematic
arithmetic error?
> + aaf->mclk_ticks = 0;
> + aaf->mclk_start_time = now.tv_sec * NSEC_PER_SEC + now.tv_nsec +
> + aaf->mclk_period;
> +
> + itspec.it_value.tv_sec = aaf->mclk_start_time / NSEC_PER_SEC;
> + itspec.it_value.tv_nsec = aaf->mclk_start_time % NSEC_PER_SEC;
> + itspec.it_interval.tv_sec = 0;
> + itspec.it_interval.tv_nsec = aaf->mclk_period;
> + res = timerfd_settime(aaf->timer_fd, TFD_TIMER_ABSTIME, &itspec, NULL);
> + if (res < 0)
> + return -errno;
> +
> + return 0;
> +}
> +
> +static int aaf_mclk_reset(snd_pcm_aaf_t *aaf)
> +{
> + aaf->mclk_start_time = 0;
> + aaf->mclk_period = 0;
> + aaf->mclk_ticks = 0;
> +
> + if (aaf->timer_fd != -1) {
> + int res;
> + struct itimerspec itspec = { 0 };
> +
> + res = timerfd_settime(aaf->timer_fd, 0, &itspec, NULL);
> + if (res < 0) {
> + SNDERR("Failed to stop media clock");
> + return res;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static uint64_t aaf_mclk_gettime(snd_pcm_aaf_t *aaf)
> +{
> + return aaf->mclk_start_time + aaf->mclk_period * aaf->mclk_ticks;
> +}
> +
> +static int aaf_tx_pdu(snd_pcm_aaf_t *aaf)
> +{
> + int res;
> + uint64_t ptime;
> + snd_pcm_sframes_t n;
> + snd_pcm_ioplug_t *io = &aaf->io;
> + snd_pcm_t *pcm = io->pcm;
> + struct avtp_stream_pdu *pdu = aaf->pdu;
> +
> + n = aaf->buffer_size - snd_pcm_avail(pcm);
> + if (n == 0) {
> + /* If there is no data in audio buffer to be transmitted,
> + * we reached an underrun state.
> + */
> + return -EPIPE;
> + }
> + if (n < aaf->frames_per_pkt) {
> + /* If there isn't enough frames to fill the AVTP packet, we
> + * drop them. This behavior is suggested by IEEE 1722-2016
> + * spec, section 7.3.5.
> + */
> + aaf_inc_hw_ptr(aaf, n);
> + return 0;
> + }
> +
> + res = snd_pcm_areas_copy_wrap(aaf->payload_areas, 0,
> + aaf->frames_per_pkt,
> + aaf->audiobuf_areas,
> + aaf->hw_ptr % aaf->buffer_size,
> + aaf->buffer_size, io->channels,
> + aaf->frames_per_pkt, io->format);
> + if (res < 0) {
> + SNDERR("Failed to copy data to AVTP payload");
> + return res;
> + }
> +
> + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_SEQ_NUM, aaf->pdu_seq++);
> + if (res < 0)
> + return res;
> +
> + ptime = aaf_mclk_gettime(aaf) + aaf->mtt + aaf->t_uncertainty;
> + res = avtp_aaf_pdu_set(pdu, AVTP_AAF_FIELD_TIMESTAMP, ptime);
> + if (res < 0)
> + return res;
> +
> + n = sendto(aaf->sk_fd, aaf->pdu, aaf->pdu_size, 0,
> + (struct sockaddr *) &aaf->sk_addr,
> + sizeof(aaf->sk_addr));
> + if (n < 0 || n != aaf->pdu_size) {
> + SNDERR("Failed to send AAF PDU");
> + return -EIO;
> + }
> +
> + aaf_inc_hw_ptr(aaf, aaf->frames_per_pkt);
> + return 0;
> +}
> +
> +static int aaf_mclk_timeout_playback(snd_pcm_aaf_t *aaf)
> +{
> + int res;
> + ssize_t n;
> + uint64_t expirations;
> +
> + n = read(aaf->timer_fd, &expirations, sizeof(uint64_t));
> + if (n < 0) {
> + SNDERR("Failed to read() timer");
> + return -errno;
> + }
> +
> + if (expirations != 1)
> + pr_debug("Missed %llu tx interval(s) ", expirations - 1);
> +
> + while (expirations--) {
> + res = aaf_tx_pdu(aaf);
> + if (res < 0)
> + return res;
> + aaf->mclk_ticks++;
> + }
> +
> + return 0;
> +}
> +
> static int aaf_close(snd_pcm_ioplug_t *io)
> {
> snd_pcm_aaf_t *aaf = io->private_data;
> @@ -159,26 +563,223 @@ static int aaf_close(snd_pcm_ioplug_t *io)
> return 0;
> }
>
> +static int aaf_hw_params(snd_pcm_ioplug_t *io,
> + snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED)
> +{
> + int res;
> + snd_pcm_aaf_t *aaf = io->private_data;
> +
> + if (io->access != SND_PCM_ACCESS_RW_INTERLEAVED)
> + return -ENOTSUP;
> +
> + if (io->buffer_size > LONG_MAX)
> + return -EINVAL;
> +
> + /* XXX: We might want to support Little Endian format in future. To
> + * achieve that, we need to convert LE samples to BE before
> + * transmitting them.
> + */
> + switch (io->format) {
> + case SND_PCM_FORMAT_S16_BE:
> + case SND_PCM_FORMAT_S24_3BE:
> + case SND_PCM_FORMAT_S32_BE:
> + case SND_PCM_FORMAT_FLOAT_BE:
> + break;
> + default:
> + return -ENOTSUP;
> + }
> +
> + switch (io->rate) {
> + case 8000:
> + case 16000:
> + case 24000:
> + case 32000:
> + case 44100:
> + case 48000:
> + case 88200:
> + case 96000:
> + case 176400:
> + case 192000:
> + break;
> + default:
> + return -ENOTSUP;
> + }
> +
> + aaf->buffer_size = io->buffer_size;
> +
> + res = aaf_init_pdu(aaf);
> + if (res < 0)
> + return res;
> +
> + res = aaf_init_audio_buffer(aaf);
> + if (res < 0)
> + goto err_free_pdu;
> +
> + res = aaf_init_areas(aaf);
> + if (res < 0)
> + goto err_free_audiobuf;
> +
> + return 0;
> +
> +err_free_audiobuf:
> + free(aaf->audiobuf);
> +err_free_pdu:
> + free(aaf->pdu);
> + return res;
> +}
> +
> +static int aaf_hw_free(snd_pcm_ioplug_t *io)
> +{
> + snd_pcm_aaf_t *aaf = io->private_data;
> +
> + free(aaf->audiobuf_areas);
> + free(aaf->payload_areas);
> + free(aaf->audiobuf);
> + free(aaf->pdu);
> + return 0;
> +}
> +
> +static int aaf_sw_params(snd_pcm_ioplug_t *io, snd_pcm_sw_params_t *params)
> +{
> + int res;
> + snd_pcm_uframes_t boundary;
> + snd_pcm_aaf_t *aaf = io->private_data;
> +
> + res = snd_pcm_sw_params_get_boundary(params, &boundary);
> + if (res < 0)
> + return res;
> +
> + if (boundary > LONG_MAX)
> + return -EINVAL;
> +
> + aaf->boundary = boundary;
> + return 0;
> +}
> +
> static snd_pcm_sframes_t aaf_pointer(snd_pcm_ioplug_t *io)
> {
> + snd_pcm_aaf_t *aaf = io->private_data;
> +
> + return aaf->hw_ptr;
> +}
> +
> +static int aaf_poll_descriptors_count(snd_pcm_ioplug_t *io ATTRIBUTE_UNUSED)
> +{
> + return FD_COUNT_PLAYBACK;
> +}
> +
> +static int aaf_poll_descriptors(snd_pcm_ioplug_t *io, struct pollfd *pfd,
> + unsigned int space)
> +{
> + snd_pcm_aaf_t *aaf = io->private_data;
> +
> + if (space != FD_COUNT_PLAYBACK)
> + return -EINVAL;
> +
> + pfd[0].fd = aaf->timer_fd;
> + pfd[0].events = POLLIN;
> + return space;
> +}
> +
> +static int aaf_poll_revents(snd_pcm_ioplug_t *io, struct pollfd *pfd,
> + unsigned int nfds, unsigned short *revents)
> +{
> + int res;
> + snd_pcm_aaf_t *aaf = io->private_data;
> +
> + if (nfds != FD_COUNT_PLAYBACK)
> + return -EINVAL;
> +
> + if (pfd[0].revents & POLLIN) {
> + res = aaf_mclk_timeout_playback(aaf);
> + if (res < 0)
> + return res;
> +
> + *revents = POLLIN;
> + }
I couldn't figure out how you use playback events and your timer. When
there are two audio clock sources or timers that's usually where the fun
begins.
> +
> + return 0;
> +}
> +
> +static int aaf_prepare(snd_pcm_ioplug_t *io)
> +{
> + int res;
> + snd_pcm_aaf_t *aaf = io->private_data;
> +
> + aaf->hw_ptr = 0;
> + res = aaf_mclk_reset(aaf);
> + if (res < 0)
> + return res;
> +
> return 0;
> }
>
> static int aaf_start(snd_pcm_ioplug_t *io)
> {
> + int res;
> + snd_pcm_aaf_t *aaf = io->private_data;
> +
> + res = aaf_init_socket(aaf);
> + if (res < 0)
> + return res;
> +
> + res = aaf_init_timer(aaf);
> + if (res < 0)
> + goto err_close_sk;
> +
> + res = aaf_mclk_start_playback(aaf);
> + if (res < 0)
> + goto err_close_timer;
> +
> return 0;
> +
> +err_close_timer:
> + close(aaf->timer_fd);
> +err_close_sk:
> + close(aaf->sk_fd);
> + return res;
> }
>
> static int aaf_stop(snd_pcm_ioplug_t *io)
> {
> + snd_pcm_aaf_t *aaf = io->private_data;
> +
> + close(aaf->timer_fd);
> + close(aaf->sk_fd);
> return 0;
> }
>
> +static snd_pcm_sframes_t aaf_transfer(snd_pcm_ioplug_t *io,
> + const snd_pcm_channel_area_t *areas,
> + snd_pcm_uframes_t offset,
> + snd_pcm_uframes_t size)
> +{
> + int res;
> + snd_pcm_aaf_t *aaf = io->private_data;
> +
> + res = snd_pcm_areas_copy_wrap(aaf->audiobuf_areas,
> + (io->appl_ptr % aaf->buffer_size),
> + aaf->buffer_size, areas, offset, size,
> + io->channels, size, io->format);
> + if (res < 0)
> + return res;
> +
> + return size;
> +}
> +
> static const snd_pcm_ioplug_callback_t aaf_callback = {
> .close = aaf_close,
> + .hw_params = aaf_hw_params,
> + .hw_free = aaf_hw_free,
> + .sw_params = aaf_sw_params,
> .pointer = aaf_pointer,
> + .poll_descriptors_count = aaf_poll_descriptors_count,
> + .poll_descriptors = aaf_poll_descriptors,
> + .poll_revents = aaf_poll_revents,
> + .prepare = aaf_prepare,
> .start = aaf_start,
> .stop = aaf_stop,
> + .transfer = aaf_transfer,
> };
>
> SND_PCM_PLUGIN_DEFINE_FUNC(aaf)
> @@ -186,12 +787,21 @@ SND_PCM_PLUGIN_DEFINE_FUNC(aaf)
> snd_pcm_aaf_t *aaf;
> int res;
>
> + /* For now the plugin only supports Playback mode i.e. AAF Talker
> + * functionality.
> + */
> + if (stream != SND_PCM_STREAM_PLAYBACK)
> + return -EINVAL;
> +
> aaf = calloc(1, sizeof(*aaf));
> if (!aaf) {
> SNDERR("Failed to allocate memory");
> return -ENOMEM;
> }
>
> + aaf->sk_fd = -1;
> + aaf->timer_fd = -1;
> +
> res = aaf_load_config(aaf, conf);
> if (res < 0)
> goto err;
> @@ -200,6 +810,7 @@ SND_PCM_PLUGIN_DEFINE_FUNC(aaf)
> aaf->io.name = "AVTP Audio Format (AAF) Plugin";
> aaf->io.callback = &aaf_callback;
> aaf->io.private_data = aaf;
> + aaf->io.flags = SND_PCM_IOPLUG_FLAG_BOUNDARY_WA;
> res = snd_pcm_ioplug_create(&aaf->io, name, stream, mode);
> if (res < 0) {
> SNDERR("Failed to create ioplug instance");
> diff --git a/doc/aaf.txt b/doc/aaf.txt
> index 24ea888..b1a3f43 100644
> --- a/doc/aaf.txt
> +++ b/doc/aaf.txt
> @@ -9,6 +9,46 @@ to transmit/receive audio samples through a Time-Sensitive Network (TSN)
> capable network. The plugin enables media applications to easily implement AVTP
> Talker and Listener functionalities.
>
> +AVTP is designed to take advantage of generalized Precision Time Protocol
> +(gPTP) and Forwarding and Queuing Enhancements for Time-Sensitive Streams
> +(FQTSS).
> +
> +gPTP ensures AVTP talkers and listeners share the same time reference so the
> +presentation time from AVTP can be used to inform when PCM samples should be
> +presented to the application layer. Thus, in order to work properly, the plugin
> +requires the system clock is synchronized with the PTP time. Such functionality
> +is provided by ptp4l and phc2sys from Linuxptp project
> +(linuxptp.sourceforge.net). ptp4l and phc2sys can be set up in many different
> +ways, below we provide an example. For further information check ptp4l(8) and
> +phc2sys(8).
> +
> +On PTP master host run the following commands. Replace $IFNAME by your PTP
> +capable NIC name. The gPTP.cfg file mentioned below can be found in
> +/usr/share/doc/linuxptp/ (depending on your distro).
> + $ ptp4l -f gPTP.cfg -i $IFNAME
> + $ phc2sys -f gPTP.cfg -c $IFNAME -s CLOCK_REALTIME -w
> +
> +On PTP slave host run:
> + $ ptp4l -f gPTP.cfg -i $IFNAME -s
> + $ phc2sys -f gPTP.cfg -a -r
> +
> +FQTSS provides bandwidth reservation and traffic prioritization for the AVTP
> +stream. Thus, in order to work properly, the plugin requires FQTSS to be
> +configured properly. The FQTSS features is supported by Linux Traffic Control
> +system through the mpqrio and cbs qdiscs. Below we provide an example to
> +configure those qdiscs in order to transmit an AAF stream with 48 kHz sampling
> +rate, 16-bit sample size, stereo. For further information on how to configure
> +it check tc-mqprio(8) and tc-cbs(8) man pages.
> +
> +Configure mpqrio (replace $HANDLE_ID by an unused handle ID):
> + $ tc qdisc add dev $IFNAME parent root handle $HANDLE_ID mqprio \
> + num_tc 3 map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 \
> + queues 1 at 0 1 at 1 2 at 2 hw 0
> +
> +Configure cbs:
> + $ tc qdisc replace dev $IFNAME parent $HANDLE_ID:1 cbs idleslope 5760 \
> + sendslope -994240 hicredit 9 locredit -89 offload 1
> +
> Dependency
> ----------
>
>
More information about the Alsa-devel
mailing list