[alsa-devel] [PATCH - AAF PCM plugin 0/7] Follow-up improvements
Hi all,
This patch series provides some follow-up improvements to the AVTP Audio Format (AAF) plugin. The highlight of this series is the implementation of a transmission offload mechanism which improves the plugin performance considerably. Details are provided in the following paragraphs.
Currently, the AVTPDU transmission interval is controlled by the AAF plugin, in software. At every timer expiration, the plugin transmits one AVTPDU. This has some implications in terms of task scheduling. For instance, in class A streams, AVTPDUs are transmitted at every 125 us which means the application task should be scheduled-in at every 125 us. In a general purpose Linux system, such scheduling interval can be hard to cope with in the long-run.
To mitigate that issue, this series introduces a transmission offload mechanism that leverages the SO_TXTIME sockopt and ETF qdisc features recently introduced to kernel 4.19. Instead of sending one AVTPDU at every timer expiration, the plugin sends several AVTPDUs at once to the kernel, configuring their Tx time so the transmission interval is maintained. The kernel can then offload packet transmission to the hardware (if the network controller supports it), providing more transmission time accuracy. This offloading mechanism enables the application to sleep for longer times, easing on task scheduling.
To illustrate the improvements provided by this series, I ran a before-after experiment. The experiment setup consisted in 2 PCs with Intel i210 card connected back-to-back running an up-to-date Archlinux with kernel 4.19.4. I ran 'aplay' for 5 minutes on one PC and captured the AVTPDUs on the other PC. The metric under evaluation is the transmission interval and it is measured by checking the 'time_delta' information from ethernet frames captured at the receiving side. If you're interested in reproducing the experiments, let me know and I can share my helper scripts.
The table below shows the experiment outcome for a Class A, stereo, 16-bit sample, 48 kHz stream. The unit is nanoseconds.
| Mean | Stdev | Min | Max | Range | -------+--------+---------+---------+---------+---------+ Before | 125000 | 1154 | 75311 | 172144 | 96833 | After | 125000 | 18 | 124960 | 125048 | 88 |
Before this patchset, the transmission interval mean is equal to the optimal value (Class A stream -> 125 us interval), and it is kept the same after the patchset. However, the dispersion measurements had improved considerably, meaning the system is consistently transmitting AVTPDUs at the correct interval.
Finally, to help the review process, here follows a quick summary of the patches within this series: * PATCH 1: fixes a type in the plugin documentation. * PATCH 2: enables the user to configure the presentation time tolerance. * PATCH 3-5: refactor the code in order to land the offload mechanism code smoothly. * PATCH 6-7: implement the transmission offload mechanism.
This series can also be found in my alsa-plugins tree in github [1].
Regards,
Andre
[1] https://github.com/aguedes/alsa-plugins
Andre Guedes (7): doc: Fix typo in AAF doc aaf: Add presentation time tolerance aaf: Refactor AVTPDU transmission routines aaf: Refactor AVTPDU reception routines aaf: Refactor timeout routines aaf: Tx multiple AVTPDUs per media clock tick aaf: AVTPDU transmission periodicity
aaf/pcm_aaf.c | 600 +++++++++++++++++++++++++++++++++----------------- configure.ac | 2 +- doc/aaf.txt | 40 +++- 3 files changed, 429 insertions(+), 213 deletions(-)
This patch fixes a typo in aaf.txt documentation.
Signed-off-by: Andre Guedes andre.guedes@intel.com --- doc/aaf.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/aaf.txt b/doc/aaf.txt index 58d9c02..cae86d8 100644 --- a/doc/aaf.txt +++ b/doc/aaf.txt @@ -47,7 +47,7 @@ Enable TAI offset to be automatically set by phc2sys: clockClass 248 clockAccuracy 0xfe \ offsetScaledLogVariance 0xffff \ currentUtcOffset 37 leap61 0 leap59 0 \ - currentUtcOffsetValid 1 p pTimescale 1 \ + currentUtcOffsetValid 1 pTimescale 1 \ timeTraceable 1 frequencyTraceable 0 timeSource 0xa0'
Synchronize system clock with PTP clock:
Different AVTP applications have different presentation time tolerance. The current version of the plugin doesn't support any tolerance so this patch extends the AAF plugin in order to enable the user to configure that tolerance value.
The presentation time tolerance is specified in microseconds and it is relevant only when the plugin is operating in capture mode. For more information see the 'Plugin Configuration' session in doc/aaf.txt
This patch also does some code refactoring and encapsulates all presentation time validation code in the new is_ptime_valid() helper function.
Signed-off-by: Andre Guedes andre.guedes@intel.com --- aaf/pcm_aaf.c | 48 +++++++++++++++++++++++++++++++++++++----------- doc/aaf.txt | 6 ++++++ 2 files changed, 43 insertions(+), 11 deletions(-)
diff --git a/aaf/pcm_aaf.c b/aaf/pcm_aaf.c index b1c3d83..8410dcf 100644 --- a/aaf/pcm_aaf.c +++ b/aaf/pcm_aaf.c @@ -60,6 +60,7 @@ typedef struct { int mtt; int t_uncertainty; snd_pcm_uframes_t frames_per_pdu; + int ptime_tolerance;
int sk_fd; int timer_fd; @@ -328,6 +329,17 @@ static int aaf_load_config(snd_pcm_aaf_t *aaf, snd_config_t *conf) goto err;
aaf->frames_per_pdu = frames_per_pdu; + } else if (strcmp(id, "ptime_tolerance") == 0) { + long ptime_tolerance; + + if (snd_config_get_integer(entry, + &ptime_tolerance) < 0) + goto err; + + if (ptime_tolerance < 0) + goto err; + + aaf->ptime_tolerance = ptime_tolerance * NSEC_PER_USEC; } else { SNDERR("Invalid configuration: %s", id); goto err; @@ -699,6 +711,27 @@ static int aaf_tx_pdu(snd_pcm_aaf_t *aaf) return 0; }
+static bool is_ptime_valid(snd_pcm_aaf_t *aaf, uint32_t avtp_time) +{ + const uint64_t exp_ptime = aaf->prev_ptime + aaf->timer_period; + const uint64_t lower_bound = exp_ptime - aaf->ptime_tolerance; + const uint64_t upper_bound = exp_ptime + aaf->ptime_tolerance; + const uint64_t ptime = (exp_ptime & 0xFFFFFFFF00000000ULL) | avtp_time; + + if (ptime < lower_bound || ptime > upper_bound) { + pr_debug("Presentation time not expected"); + return false; + } + + if (ptime < aaf_mclk_gettime(aaf)) { + pr_debug("Presentation time in the past"); + return false; + } + + aaf->prev_ptime = ptime; + return true; +} + static int aaf_rx_pdu(snd_pcm_aaf_t *aaf) { int res; @@ -753,19 +786,10 @@ static int aaf_rx_pdu(snd_pcm_aaf_t *aaf) if (res < 0) return res; } else { - uint64_t ptime = aaf->prev_ptime + aaf->timer_period; - - if (avtp_time != ptime % (1ULL << 32)) { - pr_debug("Packet dropped: PT not expected"); + if (!is_ptime_valid(aaf, avtp_time)) { + pr_debug("Packet dropped: PT not valid"); return 0; } - - if (ptime < aaf_mclk_gettime(aaf)) { - pr_debug("Packet dropped: PT in the past"); - return 0; - } - - aaf->prev_ptime = ptime; }
hw_avail = snd_pcm_ioplug_hw_avail(io, aaf->hw_virt_ptr, io->appl_ptr); @@ -950,6 +974,8 @@ static void aaf_dump(snd_pcm_ioplug_t *io, snd_output_t *out) aaf->t_uncertainty / NSEC_PER_USEC); snd_output_printf(out, " frames per AVTPDU: %lu\n", aaf->frames_per_pdu); + snd_output_printf(out, " ptime tolerance: %d\n", + aaf->ptime_tolerance / NSEC_PER_USEC); }
static int aaf_hw_params(snd_pcm_ioplug_t *io, diff --git a/doc/aaf.txt b/doc/aaf.txt index cae86d8..e72eba2 100644 --- a/doc/aaf.txt +++ b/doc/aaf.txt @@ -121,6 +121,11 @@ as follows:
* frames_per_pdu: Number of audio frames transmitted in one AVTPDU.
+ * ptime_tolerance: Presentation time tolerance in microseconds. + AVTPDUs with presentation time off by +- ptime_tolerance are not + considered invalid. This option is relevant only when operating in + capture mode. + Plugin Usage ------------
@@ -138,6 +143,7 @@ below: mtt 2000 time_uncertainty 125 frames_per_pdu 6 + ptime_tolerance 100 }
Put the above to ~/.asoundrc (or /etc/asound.conf), and use the AAF PCM virtual
This patch does some code refactoring in the AVTPDU transmission routines in order to prepare the code to support the transmission offload mechanism that will be added by upcoming patches. No functionality is added or removed by this patch.
In summary, code from aaf_tx_pdu() is moved into aaf_mclk_timeout_ playback() and into a new function introduced by this patch called aaf_tx_pdus(). Below follows more details about the code refactoring.
The function aaf_tx_pdu() is modified so it only takes care of setting the payload, sequence number and presentation time from the AVTPDU, and sending it to the kernel.
The new function aaf_tx_pdus() calculates the presentation time from each AVTPDU and calls aaf_tx_pdu() multiple times, according to the pdu count argument.
Finally, the function aaf_mclk_timeout_playback() now checks if there are frames available to the "hardware" and takes care of moving the hardware pointer forward.
Signed-off-by: Andre Guedes andre.guedes@intel.com --- aaf/pcm_aaf.c | 54 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 16 deletions(-)
diff --git a/aaf/pcm_aaf.c b/aaf/pcm_aaf.c index 8410dcf..1ad1586 100644 --- a/aaf/pcm_aaf.c +++ b/aaf/pcm_aaf.c @@ -661,28 +661,18 @@ static uint64_t aaf_mclk_gettime(snd_pcm_aaf_t *aaf) (aaf->timer_expirations - 1); }
-static int aaf_tx_pdu(snd_pcm_aaf_t *aaf) +static int aaf_tx_pdu(snd_pcm_aaf_t *aaf, snd_pcm_uframes_t ptr, + uint64_t ptime) { int res; - uint64_t ptime; ssize_t n; - snd_pcm_uframes_t hw_avail; snd_pcm_ioplug_t *io = &aaf->io; struct avtp_stream_pdu *pdu = aaf->pdu;
- hw_avail = snd_pcm_ioplug_hw_avail(io, aaf->hw_ptr, io->appl_ptr); - if (hw_avail < aaf->frames_per_pdu) { - /* If the number of available frames is less than number of - * frames needed to fill an AVTPDU, we reached an underrun - * state. - */ - return -EPIPE; - } - res = snd_pcm_areas_copy_wrap(aaf->payload_areas, 0, aaf->frames_per_pdu, aaf->audiobuf_areas, - (aaf->hw_ptr % io->buffer_size), + (ptr % io->buffer_size), io->buffer_size, io->channels, aaf->frames_per_pdu, io->format); if (res < 0) { @@ -694,7 +684,6 @@ static int aaf_tx_pdu(snd_pcm_aaf_t *aaf) 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; @@ -707,7 +696,27 @@ static int aaf_tx_pdu(snd_pcm_aaf_t *aaf) return -EIO; }
- aaf_inc_ptr(&aaf->hw_ptr, aaf->frames_per_pdu, aaf->boundary); + return 0; +} + +static int aaf_tx_pdus(snd_pcm_aaf_t *aaf, int pdu_count) +{ + int res; + uint64_t ptime; + snd_pcm_uframes_t ptr; + + ptime = aaf_mclk_gettime(aaf) + aaf->mtt + aaf->t_uncertainty; + ptr = aaf->hw_ptr; + + while (pdu_count--) { + res = aaf_tx_pdu(aaf, ptr, ptime); + if (res < 0) + return res; + + ptime += aaf->timer_period; + ptr += aaf->frames_per_pdu; + } + return 0; }
@@ -845,6 +854,8 @@ static int aaf_mclk_timeout_playback(snd_pcm_aaf_t *aaf) int res; ssize_t n; uint64_t expirations; + snd_pcm_uframes_t hw_avail; + snd_pcm_ioplug_t *io = &aaf->io;
n = read(aaf->timer_fd, &expirations, sizeof(uint64_t)); if (n < 0) { @@ -858,9 +869,20 @@ static int aaf_mclk_timeout_playback(snd_pcm_aaf_t *aaf) while (expirations--) { aaf->timer_expirations++;
- res = aaf_tx_pdu(aaf); + hw_avail = snd_pcm_ioplug_hw_avail(io, aaf->hw_ptr, io->appl_ptr); + if (hw_avail < aaf->frames_per_pdu) { + /* If the number of available frames is less than + * number of frames needed to fill an AVTPDU, we + * reached an underrun state. + */ + return -EPIPE; + } + + res = aaf_tx_pdus(aaf, 1); if (res < 0) return res; + + aaf_inc_ptr(&aaf->hw_ptr, aaf->frames_per_pdu, aaf->boundary); }
return 0;
This patch does some code refactoring in the AVTPDU reception routines in order to prepare the code to support the transmission offload mechanism that will be added by upcoming patches. No functionality is added or removed by this patch.
In summary, the function aaf_rx_pdu() is broken down into smaller, new functions and some code is moved to is_pdu_valid(). Below follows more details about the code refactoring.
The function aaf_rx_pdu() was renamed to aaf_socket_new_data() which reads the socket and dispatches the AVTPDU in case the PCM device isn't in DRAIN state. Function aaf_dispatch_pdu() simply reads the subtype field from the AVTPDU and dispatches it according. For now, only AAF PDUs are supported. Finally, the aaf_dispatch_pdu_aaf() handles AAF AVTPDUs which means to check if the AVTPDU is valid, copies the PCM samples from the payload to the audio buffer, and start the media clock in case it hasn't been started already.
The function is_pdu_valid() is moved around to avoid forward declaration. Code to validate the sequence number and presentation time from the AVTPDU which used to be in aaf_rx_pdu() is moved into is_pdu_valid().
Signed-off-by: Andre Guedes andre.guedes@intel.com --- aaf/pcm_aaf.c | 320 +++++++++++++++++++++++++++++--------------------- 1 file changed, 183 insertions(+), 137 deletions(-)
diff --git a/aaf/pcm_aaf.c b/aaf/pcm_aaf.c index 1ad1586..49d5bed 100644 --- a/aaf/pcm_aaf.c +++ b/aaf/pcm_aaf.c @@ -129,106 +129,6 @@ static unsigned int alsa_to_avtp_rate(unsigned int rate) } }
-static bool is_pdu_valid(struct avtp_stream_pdu *pdu, uint64_t streamid, - unsigned int data_len, unsigned int format, - unsigned int nsr, unsigned int channels, - unsigned int depth) -{ - int res; - uint64_t val64; - uint32_t val32; - struct avtp_common_pdu *common = (struct avtp_common_pdu *) pdu; - - res = avtp_pdu_get(common, AVTP_FIELD_SUBTYPE, &val32); - if (res < 0) - return false; - if (val32 != AVTP_SUBTYPE_AAF) { - pr_debug("Subtype mismatch: expected %u, got %u", - AVTP_SUBTYPE_AAF, val32); - return false; - } - - res = avtp_pdu_get(common, AVTP_FIELD_VERSION, &val32); - if (res < 0) - return false; - if (val32 != 0) { - pr_debug("Version mismatch: expected %u, got %u", 0, val32); - return false; - } - - res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_STREAM_ID, &val64); - if (res < 0) - return false; - if (val64 != streamid) { - pr_debug("Streamid mismatch: expected %lu, got %lu", streamid, - val64); - return false; - } - - res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_TV, &val64); - if (res < 0) - return false; - if (val64 != 1) { - pr_debug("TV mismatch: expected %u, got %lu", 1, val64); - return false; - } - - res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_SP, &val64); - if (res < 0) - return false; - if (val64 != AVTP_AAF_PCM_SP_NORMAL) { - pr_debug("SP mismatch: expected %u, got %lu", - AVTP_AAF_PCM_SP_NORMAL, val64); - return false; - } - - res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_FORMAT, &val64); - if (res < 0) - return false; - if (val64 != format) { - pr_debug("Format mismatch: expected %u, got %lu", format, - val64); - return false; - } - - res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_NSR, &val64); - if (res < 0) - return false; - if (val64 != nsr) { - pr_debug("NSR mismatch: expected %u, got %lu", nsr, val64); - return false; - } - - res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_CHAN_PER_FRAME, &val64); - if (res < 0) - return false; - if (val64 != channels) { - pr_debug("Channels mismatch: expected %u, got %lu", channels, - val64); - return false; - } - - res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_BIT_DEPTH, &val64); - if (res < 0) - return false; - if (val64 != depth) { - pr_debug("Bit depth mismatch: expected %u, got %lu", depth, - val64); - return false; - } - - res = avtp_aaf_pdu_get(pdu, AVTP_AAF_FIELD_STREAM_DATA_LEN, &val64); - if (res < 0) - return false; - if (val64 != data_len) { - pr_debug("Data len mismatch: expected %u, got %lu", - data_len, val64); - return false; - } - - return true; -} - static int aaf_load_config(snd_pcm_aaf_t *aaf, snd_config_t *conf) { snd_config_iterator_t cur, next; @@ -741,66 +641,134 @@ static bool is_ptime_valid(snd_pcm_aaf_t *aaf, uint32_t avtp_time) return true; }
-static int aaf_rx_pdu(snd_pcm_aaf_t *aaf) +static bool is_pdu_valid(snd_pcm_aaf_t *aaf) { int res; - ssize_t n; - uint64_t seq, avtp_time; - snd_pcm_uframes_t hw_avail; + uint64_t val64; + uint32_t val32; snd_pcm_ioplug_t *io = &aaf->io; snd_pcm_t *pcm = io->pcm; + const uint64_t data_len = snd_pcm_frames_to_bytes(pcm, aaf->frames_per_pdu); + const uint64_t format = alsa_to_avtp_format(io->format); + const uint64_t nsr = alsa_to_avtp_rate(io->rate); + const uint64_t depth = snd_pcm_format_width(io->format); + struct avtp_common_pdu *common = (struct avtp_common_pdu *) aaf->pdu;
- n = recv(aaf->sk_fd, aaf->pdu, aaf->pdu_size, 0); - if (n < 0) { - SNDERR("Failed to receive data"); - return -errno; + res = avtp_pdu_get(common, AVTP_FIELD_VERSION, &val32); + if (res < 0) + return false; + if (val32 != 0) { + pr_debug("Version mismatch: expected %u, got %u", 0, val32); + return false; } - if (n != aaf->pdu_size) { - pr_debug("AVTPDU dropped: Invalid size"); - return 0; + + res = avtp_aaf_pdu_get(aaf->pdu, AVTP_AAF_FIELD_STREAM_ID, &val64); + if (res < 0) + return false; + if (val64 != aaf->streamid) { + pr_debug("Streamid mismatch: expected %lu, got %lu", + aaf->streamid, val64); + return false; }
- if (io->state == SND_PCM_STATE_DRAINING) { - /* If device is in DRAIN state, we shouldn't copy any more data - * to audio buffer. So we are done here. - */ - return 0; + res = avtp_aaf_pdu_get(aaf->pdu, AVTP_AAF_FIELD_TV, &val64); + if (res < 0) + return false; + if (val64 != 1) { + pr_debug("TV mismatch: expected %u, got %lu", 1, val64); + return false; }
- if (!is_pdu_valid(aaf->pdu, aaf->streamid, - snd_pcm_frames_to_bytes(pcm, aaf->frames_per_pdu), - alsa_to_avtp_format(io->format), - alsa_to_avtp_rate(io->rate), - io->channels, snd_pcm_format_width(io->format))) { - pr_debug("AVTPDU dropped: Bad field(s)"); - return 0; + res = avtp_aaf_pdu_get(aaf->pdu, AVTP_AAF_FIELD_SP, &val64); + if (res < 0) + return false; + if (val64 != AVTP_AAF_PCM_SP_NORMAL) { + pr_debug("SP mismatch: expected %u, got %lu", + AVTP_AAF_PCM_SP_NORMAL, val64); + return false; }
- res = avtp_aaf_pdu_get(aaf->pdu, AVTP_AAF_FIELD_SEQ_NUM, &seq); + res = avtp_aaf_pdu_get(aaf->pdu, AVTP_AAF_FIELD_FORMAT, &val64); if (res < 0) - return res; - if (seq != aaf->pdu_seq) { + return false; + if (val64 != format) { + pr_debug("Format mismatch: expected %u, got %lu", format, + val64); + return false; + } + + res = avtp_aaf_pdu_get(aaf->pdu, AVTP_AAF_FIELD_NSR, &val64); + if (res < 0) + return false; + if (val64 != nsr) { + pr_debug("NSR mismatch: expected %u, got %lu", nsr, val64); + return false; + } + + res = avtp_aaf_pdu_get(aaf->pdu, AVTP_AAF_FIELD_CHAN_PER_FRAME, &val64); + if (res < 0) + return false; + if (val64 != io->channels) { + pr_debug("Channels mismatch: expected %u, got %lu", + io->channels, val64); + return false; + } + + res = avtp_aaf_pdu_get(aaf->pdu, AVTP_AAF_FIELD_BIT_DEPTH, &val64); + if (res < 0) + return false; + if (val64 != depth) { + pr_debug("Bit depth mismatch: expected %u, got %lu", depth, + val64); + return false; + } + + res = avtp_aaf_pdu_get(aaf->pdu, AVTP_AAF_FIELD_STREAM_DATA_LEN, &val64); + if (res < 0) + return false; + if (val64 != data_len) { + pr_debug("Data len mismatch: expected %u, got %lu", + data_len, val64); + return false; + } + + res = avtp_aaf_pdu_get(aaf->pdu, AVTP_AAF_FIELD_SEQ_NUM, &val64); + if (res < 0) + return false; + if (val64 != aaf->pdu_seq) { pr_debug("Sequence mismatch: expected %u, got %lu", - aaf->pdu_seq, seq); - aaf->pdu_seq = seq; + aaf->pdu_seq, val64); + aaf->pdu_seq = val64; } aaf->pdu_seq++;
- res = avtp_aaf_pdu_get(aaf->pdu, AVTP_AAF_FIELD_TIMESTAMP, &avtp_time); - if (res < 0) - return res; + if (aaf->timer_starttime) { + /* If media clock has started, it means we have already + * received an AVTPDU, so we are able to check if the + * Presentation Time from this AVTPDU is valid. + */ + uint64_t avtp_time;
- if (aaf->timer_starttime == 0) { - res = aaf_mclk_start_capture(aaf, avtp_time); + res = avtp_aaf_pdu_get(aaf->pdu, AVTP_AAF_FIELD_TIMESTAMP, + &avtp_time); if (res < 0) - return res; - } else { + return false; + if (!is_ptime_valid(aaf, avtp_time)) { pr_debug("Packet dropped: PT not valid"); - return 0; + return false; } }
+ return true; +} + +static int aaf_copy_pdu_payload(snd_pcm_aaf_t *aaf) +{ + int res; + snd_pcm_uframes_t hw_avail; + snd_pcm_ioplug_t *io = &aaf->io; + hw_avail = snd_pcm_ioplug_hw_avail(io, aaf->hw_virt_ptr, io->appl_ptr); if (hw_avail < aaf->frames_per_pdu) { /* If there isn't enough space available on buffer to copy the @@ -824,6 +792,84 @@ static int aaf_rx_pdu(snd_pcm_aaf_t *aaf) return 0; }
+static int aaf_dispatch_pdu_aaf(snd_pcm_aaf_t *aaf) +{ + int res; + + if (!is_pdu_valid(aaf)) { + pr_debug("AAF PDU dropped: Bad field(s)"); + return 0; + } + + res = aaf_copy_pdu_payload(aaf); + if (res < 0) + return res; + + if (aaf->timer_starttime == 0) { + /* If the media clock has not been started yet (which means + * this is the first AAF PDU received by the plugin), we start + * it. + */ + uint64_t avtp_time; + + res = avtp_aaf_pdu_get(aaf->pdu, AVTP_AAF_FIELD_TIMESTAMP, + &avtp_time); + if (res < 0) + return res; + + res = aaf_mclk_start_capture(aaf, avtp_time); + if (res < 0) + return res; + } + + return 0; +} + +static int aaf_dispatch_pdu(snd_pcm_aaf_t *aaf) +{ + int res; + uint32_t subtype; + struct avtp_common_pdu *common = (struct avtp_common_pdu *) aaf->pdu; + + res = avtp_pdu_get(common, AVTP_FIELD_SUBTYPE, &subtype); + if (res < 0) + return res; + + switch (subtype) { + case AVTP_SUBTYPE_AAF: + return aaf_dispatch_pdu_aaf(aaf); + default: + pr_debug("AVTPDU dropped: subtype not supported"); + return 0; + } +} + +static int aaf_socket_new_data(snd_pcm_aaf_t *aaf) +{ + ssize_t n; + snd_pcm_ioplug_t *io = &aaf->io; + + n = recv(aaf->sk_fd, aaf->pdu, aaf->pdu_size, 0); + if (n < 0) { + SNDERR("Failed to receive data"); + return -errno; + } + if (n != aaf->pdu_size) { + pr_debug("AVTPDU dropped: Invalid size"); + return 0; + } + + if (io->state == SND_PCM_STATE_DRAINING) { + /* If device is in DRAIN state, there is no point in + * dispatching the AVTPDU just received so we are done + * here. + */ + return 0; + } + + return aaf_dispatch_pdu(aaf); +} + static int aaf_flush_rx_buf(snd_pcm_aaf_t *aaf) { char *tmp; @@ -1125,7 +1171,7 @@ static int aaf_poll_revents(snd_pcm_ioplug_t *io, struct pollfd *pfd, }
if (pfd[1].revents & POLLIN) { - res = aaf_rx_pdu(aaf); + res = aaf_socket_new_data(aaf); if (res < 0) return res; }
The functions aaf_mclk_timeout_playback() and aaf_mclk_timeout_capture() have some common code so this patch does a code refactoring by moving the shared code into a new function called aaf_timer_timeout().
After the refactoring, aaf_mclk_timeout_playback() and aaf_mclk_timeout_ capture() ended up having no code related to timeout so they were renamed to better represent what they really do (send frames and present frames, respectively).
Signed-off-by: Andre Guedes andre.guedes@intel.com --- aaf/pcm_aaf.c | 87 ++++++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 42 deletions(-)
diff --git a/aaf/pcm_aaf.c b/aaf/pcm_aaf.c index 49d5bed..1d7ebca 100644 --- a/aaf/pcm_aaf.c +++ b/aaf/pcm_aaf.c @@ -895,51 +895,64 @@ static int aaf_flush_rx_buf(snd_pcm_aaf_t *aaf) return 0; }
-static int aaf_mclk_timeout_playback(snd_pcm_aaf_t *aaf) +static int aaf_tx_frames(snd_pcm_aaf_t *aaf) { int res; - ssize_t n; - uint64_t expirations; snd_pcm_uframes_t hw_avail; snd_pcm_ioplug_t *io = &aaf->io;
- n = read(aaf->timer_fd, &expirations, sizeof(uint64_t)); - if (n < 0) { - SNDERR("Failed to read() timer"); - return -errno; + hw_avail = snd_pcm_ioplug_hw_avail(io, aaf->hw_ptr, io->appl_ptr); + if (hw_avail < aaf->frames_per_pdu) { + /* If the number of available frames is less than number of + * frames needed to fill an AVTPDU, we reached an underrun + * state. + */ + return -EPIPE; }
- if (expirations != 1) - pr_debug("Missed %llu tx interval(s) ", expirations - 1); + res = aaf_tx_pdus(aaf, 1); + if (res < 0) + return res;
- while (expirations--) { - aaf->timer_expirations++; + aaf_inc_ptr(&aaf->hw_ptr, aaf->frames_per_pdu, aaf->boundary); + return 0; +}
- hw_avail = snd_pcm_ioplug_hw_avail(io, aaf->hw_ptr, io->appl_ptr); - if (hw_avail < aaf->frames_per_pdu) { - /* If the number of available frames is less than - * number of frames needed to fill an AVTPDU, we - * reached an underrun state. - */ - return -EPIPE; - } +static int aaf_present_frames(snd_pcm_aaf_t *aaf) +{ + snd_pcm_sframes_t len; + snd_pcm_ioplug_t *io = &aaf->io;
- res = aaf_tx_pdus(aaf, 1); - if (res < 0) - return res; + len = aaf->hw_virt_ptr - aaf->hw_ptr; + if (len < 0) + len += aaf->boundary;
- aaf_inc_ptr(&aaf->hw_ptr, aaf->frames_per_pdu, aaf->boundary); + if ((snd_pcm_uframes_t) len > io->buffer_size) { + /* If the distance between hw virtual pointer and hw + * pointer is greater than the buffer size, it means we + * had an overrun error so -EPIPE is returned. + */ + return -EPIPE; }
+ aaf_inc_ptr(&aaf->hw_ptr, aaf->frames_per_pdu, aaf->boundary); return 0; }
-static int aaf_mclk_timeout_capture(snd_pcm_aaf_t *aaf) +static int aaf_process_frames(snd_pcm_aaf_t *aaf) { + snd_pcm_ioplug_t *io = &aaf->io; + + return (io->stream == SND_PCM_STREAM_PLAYBACK) ? + aaf_tx_frames(aaf) : + aaf_present_frames(aaf); +} + +static int aaf_timer_timeout(snd_pcm_aaf_t *aaf) +{ + int res; ssize_t n; uint64_t expirations; - snd_pcm_sframes_t len; - snd_pcm_ioplug_t *io = &aaf->io;
n = read(aaf->timer_fd, &expirations, sizeof(uint64_t)); if (n < 0) { @@ -948,24 +961,14 @@ static int aaf_mclk_timeout_capture(snd_pcm_aaf_t *aaf) }
if (expirations != 1) - pr_debug("Missed %llu presentation time(s) ", expirations - 1); + pr_debug("Missed %llu expirations ", expirations - 1);
while (expirations--) { aaf->timer_expirations++;
- len = aaf->hw_virt_ptr - aaf->hw_ptr; - if (len < 0) - len += aaf->boundary; - - if ((snd_pcm_uframes_t) len > io->buffer_size) { - /* If the distance between hw virtual pointer and hw - * pointer is greater than the buffer size, it means we - * had an overrun error so -EPIPE is returned. - */ - return -EPIPE; - } - - aaf_inc_ptr(&aaf->hw_ptr, aaf->frames_per_pdu, aaf->boundary); + res = aaf_process_frames(aaf); + if (res < 0) + return res; }
return 0; @@ -1152,7 +1155,7 @@ static int aaf_poll_revents(snd_pcm_ioplug_t *io, struct pollfd *pfd, return -EINVAL;
if (pfd[0].revents & POLLIN) { - res = aaf_mclk_timeout_playback(aaf); + res = aaf_timer_timeout(aaf); if (res < 0) return res;
@@ -1163,7 +1166,7 @@ static int aaf_poll_revents(snd_pcm_ioplug_t *io, struct pollfd *pfd, return -EINVAL;
if (pfd[0].revents & POLLIN) { - res = aaf_mclk_timeout_capture(aaf); + res = aaf_timer_timeout(aaf); if (res < 0) return res;
In order to implement the transmission offload mechanism, we need to change the way the plugin behaves so, instead of sending only one AVTPDU, it sends several AVTPDUs at every media clock tick.
To achieve that, this patch changes the plugin so it consumes the audio buffer in chunks of period size instead of in frames_per_pdu. The number of AVTPDUs sent at every media clock tick is calculated by dividing the period size by the number of frames per AVTPDU. To ensure that number is not fractional, the plugin requires that the period size is multiple of the plugin configuration 'frames_per_pdu'.
For the sake of consistency, the capture mode is also changed so the plugin presents audio frames to the alsa-lib layer in chunks of period size as well.
Signed-off-by: Andre Guedes andre.guedes@intel.com --- aaf/pcm_aaf.c | 44 +++++++++++++++++++++++++++++++------------- doc/aaf.txt | 7 ++++--- 2 files changed, 35 insertions(+), 16 deletions(-)
diff --git a/aaf/pcm_aaf.c b/aaf/pcm_aaf.c index 1d7ebca..284cbfd 100644 --- a/aaf/pcm_aaf.c +++ b/aaf/pcm_aaf.c @@ -83,6 +83,8 @@ typedef struct { snd_pcm_uframes_t boundary;
uint64_t prev_ptime; + + int pdu_period; } snd_pcm_aaf_t;
static unsigned int alsa_to_avtp_format(snd_pcm_format_t format) @@ -489,7 +491,7 @@ static int aaf_mclk_start_playback(snd_pcm_aaf_t *aaf) return -errno; }
- period = (uint64_t)NSEC_PER_SEC * aaf->frames_per_pdu / io->rate; + period = (uint64_t)NSEC_PER_SEC * io->period_size / io->rate; time = now.tv_sec * NSEC_PER_SEC + now.tv_nsec + period; res = aaf_mclk_start(aaf, time, period); if (res < 0) @@ -502,7 +504,7 @@ static int aaf_mclk_start_capture(snd_pcm_aaf_t *aaf, uint32_t avtp_time) { int res; struct timespec tspec; - uint64_t now, ptime, period; + uint64_t now, ptime, time, period; snd_pcm_ioplug_t *io = &aaf->io;
res = clock_gettime(CLOCK_TAI, &tspec); @@ -526,8 +528,9 @@ static int aaf_mclk_start_capture(snd_pcm_aaf_t *aaf, uint32_t avtp_time) if (ptime < now) ptime += (1ULL << 32);
- period = (uint64_t)NSEC_PER_SEC * aaf->frames_per_pdu / io->rate; - res = aaf_mclk_start(aaf, ptime, period); + period = (uint64_t)NSEC_PER_SEC * io->period_size / io->rate; + time = ptime + period; + res = aaf_mclk_start(aaf, time, period); if (res < 0) return res;
@@ -613,7 +616,7 @@ static int aaf_tx_pdus(snd_pcm_aaf_t *aaf, int pdu_count) if (res < 0) return res;
- ptime += aaf->timer_period; + ptime += aaf->pdu_period; ptr += aaf->frames_per_pdu; }
@@ -622,7 +625,7 @@ static int aaf_tx_pdus(snd_pcm_aaf_t *aaf, int pdu_count)
static bool is_ptime_valid(snd_pcm_aaf_t *aaf, uint32_t avtp_time) { - const uint64_t exp_ptime = aaf->prev_ptime + aaf->timer_period; + const uint64_t exp_ptime = aaf->prev_ptime + aaf->pdu_period; const uint64_t lower_bound = exp_ptime - aaf->ptime_tolerance; const uint64_t upper_bound = exp_ptime + aaf->ptime_tolerance; const uint64_t ptime = (exp_ptime & 0xFFFFFFFF00000000ULL) | avtp_time; @@ -899,22 +902,23 @@ static int aaf_tx_frames(snd_pcm_aaf_t *aaf) { int res; snd_pcm_uframes_t hw_avail; + int pdu_count; snd_pcm_ioplug_t *io = &aaf->io;
hw_avail = snd_pcm_ioplug_hw_avail(io, aaf->hw_ptr, io->appl_ptr); - if (hw_avail < aaf->frames_per_pdu) { - /* If the number of available frames is less than number of - * frames needed to fill an AVTPDU, we reached an underrun - * state. + if (hw_avail < io->period_size) { + /* If the number of available frames is less than the period + * size, we reached an underrun state. */ return -EPIPE; }
- res = aaf_tx_pdus(aaf, 1); + pdu_count = io->period_size / aaf->frames_per_pdu; + res = aaf_tx_pdus(aaf, pdu_count); if (res < 0) return res;
- aaf_inc_ptr(&aaf->hw_ptr, aaf->frames_per_pdu, aaf->boundary); + aaf_inc_ptr(&aaf->hw_ptr, io->period_size, aaf->boundary); return 0; }
@@ -935,7 +939,7 @@ static int aaf_present_frames(snd_pcm_aaf_t *aaf) return -EPIPE; }
- aaf_inc_ptr(&aaf->hw_ptr, aaf->frames_per_pdu, aaf->boundary); + aaf_inc_ptr(&aaf->hw_ptr, io->period_size, aaf->boundary); return 0; }
@@ -1071,8 +1075,22 @@ static int aaf_hw_params(snd_pcm_ioplug_t *io, if (res < 0) goto err_free_pdu;
+ if (io->period_size % aaf->frames_per_pdu) { + /* The plugin requires that the period size is multiple of the + * configuration frames_per_pdu. Return error if this + * requirement isn't satisfied. + */ + SNDERR("Period size must be multiple of frames_per_pdu"); + res = -EINVAL; + goto err_free_areas; + } + + aaf->pdu_period = (uint64_t)NSEC_PER_SEC * aaf->frames_per_pdu / + io->rate; return 0;
+err_free_areas: + free(aaf->payload_areas); err_free_pdu: free(aaf->pdu); err_close_timer: diff --git a/doc/aaf.txt b/doc/aaf.txt index e72eba2..e12a6f6 100644 --- a/doc/aaf.txt +++ b/doc/aaf.txt @@ -147,16 +147,17 @@ below: }
Put the above to ~/.asoundrc (or /etc/asound.conf), and use the AAF PCM virtual -device 'aaf0' with your favorite alsa-utils tool. +device 'aaf0' with your favorite alsa-utils tool. Note that the plugin requires +the period size is multiple of the 'frames_per_pdu' configuration.
For example, to stream the pink noise generated by 'speaker-test', run:
- $ speaker-test -F S16_BE -c 2 -r 48000 -D aaf0 + $ speaker-test -F S16_BE -c 2 -r 48000 -D aaf0 -p 12500
To receive the AAF stream generated by the command above, run the following command in another host:
- $ arecord -t raw -f S16_BE -c 2 -r 48000 -D aaf0 -vv /dev/null + $ arecord -t raw -f S16_BE -c 2 -r 48000 -D aaf0 -vv /dev/null -F 12500
If you want to playback the contents of the AAF stream, change the command-line above so the output from 'arecord' is redirected to 'aplay', or simply use the
When operating in playback mode (i.e. AVTP talker) the plugin is expected to transmit the AVTPDUs in a periodical manner. The AVTPDU period is defined by the number of audio frames per AVTPDU and the sampling rate (see section 7.7 from AVTP spec [1] for further information).
To enforce the AVTPDU periodicity, this patch leverages the SO_TXTIME sockopt recently added to socket interface which enables the userspace to specify when a given packet should be transmitted. The plugin configures the transmission time from each AVTPDU so the expected transmission interval is maintained.
The SO_TXTIME feature works in conjunction with the Earliest TxTime First (ETF) qdisc. The ETF qdisc sorts packets from multiple sockets by the earliest transmission time and sends them to the network controller. It also enables offloading packet transmission to hardware in case the NIC supports it, providing more time accuracy. For further information about ETF qdisc, see tc-etf(8). The qdisc can be configured many ways, in doc/aaf.txt we provide an example.
Below follows some implementation highlights:
The packet transmission time is configured through socket control message interface so we now use sendmsg() to transmit AVTPDUs, instead of sendto().
sendmsg() API requires a msghdr struct which is initialized during device setup time. Strictly speaking, that struct is only required when operating in playback mode but we initialize it always, no matter if running in playback or capture mode. This makes hw_params() and hw_free() callbacks implementation way more simpler, specially on handling error cases.
[1] 1722-2016 - IEEE Standard for a Transport Protocol for Time-Sensitive Applications in Bridged Local Area Networks
Signed-off-by: Andre Guedes andre.guedes@intel.com --- aaf/pcm_aaf.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++---- configure.ac | 2 +- doc/aaf.txt | 25 +++++++++---- 3 files changed, 111 insertions(+), 17 deletions(-)
diff --git a/aaf/pcm_aaf.c b/aaf/pcm_aaf.c index 284cbfd..cc7dbe3 100644 --- a/aaf/pcm_aaf.c +++ b/aaf/pcm_aaf.c @@ -27,6 +27,7 @@ #include <limits.h> #include <linux/if_ether.h> #include <linux/if_packet.h> +#include <linux/net_tstamp.h> #include <net/if.h> #include <string.h> #include <stdbool.h> @@ -71,6 +72,9 @@ typedef struct { int pdu_size; uint8_t pdu_seq;
+ struct msghdr *msg; + struct cmsghdr *cmsg; + uint64_t timer_starttime; uint64_t timer_period; uint64_t timer_expirations; @@ -282,6 +286,8 @@ static int aaf_init_socket(snd_pcm_aaf_t *aaf) memcpy(&aaf->sk_addr.sll_addr, aaf->addr, ETH_ALEN);
if (io->stream == SND_PCM_STREAM_PLAYBACK) { + struct sock_txtime txtime_cfg; + res = setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &aaf->prio, sizeof(aaf->prio)); if (res < 0) { @@ -289,6 +295,16 @@ static int aaf_init_socket(snd_pcm_aaf_t *aaf) res = -errno; goto err; } + + txtime_cfg.clockid = CLOCK_TAI; + txtime_cfg.flags = 0; + res = setsockopt(fd, SOL_SOCKET, SO_TXTIME, &txtime_cfg, + sizeof(txtime_cfg)); + if (res < 0) { + SNDERR("Failed to configure txtime"); + res = -errno; + goto err; + } } else { struct packet_mreq mreq = { 0 };
@@ -447,6 +463,62 @@ err: return res; }
+static int aaf_init_msghdr(snd_pcm_aaf_t *aaf) +{ + int res; + struct iovec *iov; + char *control; + size_t controllen; + struct msghdr *msg; + struct cmsghdr *cmsg; + + iov = malloc(sizeof(struct iovec)); + if (!iov) { + SNDERR("Failed to allocate iovec"); + return -ENOMEM; + } + + iov->iov_base = aaf->pdu; + iov->iov_len = aaf->pdu_size; + + controllen = CMSG_SPACE(sizeof(__u64)); + control = malloc(controllen); + if (!control) { + SNDERR("Failed to allocate control buffer"); + res = -ENOMEM; + goto err_free_iov; + } + + msg = malloc(sizeof(struct msghdr)); + if (!msg) { + SNDERR("Failed to allocate msghdr"); + res = -ENOMEM; + goto err_free_control; + } + + msg->msg_name = &aaf->sk_addr; + msg->msg_namelen = sizeof(aaf->sk_addr); + msg->msg_iov = iov; + msg->msg_iovlen = 1; + msg->msg_control = control; + msg->msg_controllen = controllen; + + cmsg = CMSG_FIRSTHDR(msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_TXTIME; + cmsg->cmsg_len = CMSG_LEN(sizeof(__u64)); + + aaf->msg = msg; + aaf->cmsg = cmsg; + return 0; + +err_free_control: + free(control); +err_free_iov: + free(iov); + return res; +} + static void aaf_inc_ptr(snd_pcm_uframes_t *ptr, snd_pcm_uframes_t val, snd_pcm_uframes_t boundary) { @@ -565,13 +637,15 @@ static uint64_t aaf_mclk_gettime(snd_pcm_aaf_t *aaf) }
static int aaf_tx_pdu(snd_pcm_aaf_t *aaf, snd_pcm_uframes_t ptr, - uint64_t ptime) + uint64_t ptime, __u64 txtime) { int res; ssize_t n; snd_pcm_ioplug_t *io = &aaf->io; struct avtp_stream_pdu *pdu = aaf->pdu;
+ *(__u64 *)CMSG_DATA(aaf->cmsg) = txtime; + res = snd_pcm_areas_copy_wrap(aaf->payload_areas, 0, aaf->frames_per_pdu, aaf->audiobuf_areas, @@ -591,9 +665,7 @@ static int aaf_tx_pdu(snd_pcm_aaf_t *aaf, snd_pcm_uframes_t ptr, 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)); + n = sendmsg(aaf->sk_fd, aaf->msg, 0); if (n < 0 || n != aaf->pdu_size) { SNDERR("Failed to send AAF PDU"); return -EIO; @@ -605,17 +677,19 @@ static int aaf_tx_pdu(snd_pcm_aaf_t *aaf, snd_pcm_uframes_t ptr, static int aaf_tx_pdus(snd_pcm_aaf_t *aaf, int pdu_count) { int res; - uint64_t ptime; + uint64_t ptime, txtime; snd_pcm_uframes_t ptr;
- ptime = aaf_mclk_gettime(aaf) + aaf->mtt + aaf->t_uncertainty; + txtime = aaf_mclk_gettime(aaf) + aaf->t_uncertainty; + ptime = txtime + aaf->mtt; ptr = aaf->hw_ptr;
while (pdu_count--) { - res = aaf_tx_pdu(aaf, ptr, ptime); + res = aaf_tx_pdu(aaf, ptr, ptime, txtime); if (res < 0) return res;
+ txtime += aaf->pdu_period; ptime += aaf->pdu_period; ptr += aaf->frames_per_pdu; } @@ -1075,6 +1149,10 @@ static int aaf_hw_params(snd_pcm_ioplug_t *io, if (res < 0) goto err_free_pdu;
+ res = aaf_init_msghdr(aaf); + if (res < 0) + goto err_free_areas; + if (io->period_size % aaf->frames_per_pdu) { /* The plugin requires that the period size is multiple of the * configuration frames_per_pdu. Return error if this @@ -1082,13 +1160,17 @@ static int aaf_hw_params(snd_pcm_ioplug_t *io, */ SNDERR("Period size must be multiple of frames_per_pdu"); res = -EINVAL; - goto err_free_areas; + goto err_free_msghdr; }
aaf->pdu_period = (uint64_t)NSEC_PER_SEC * aaf->frames_per_pdu / io->rate; return 0;
+err_free_msghdr: + free(aaf->msg->msg_iov); + free(aaf->msg->msg_control); + free(aaf->msg); err_free_areas: free(aaf->payload_areas); err_free_pdu: @@ -1108,6 +1190,9 @@ static int aaf_hw_free(snd_pcm_ioplug_t *io) close(aaf->timer_fd); free(aaf->pdu); free(aaf->payload_areas); + free(aaf->msg->msg_iov); + free(aaf->msg->msg_control); + free(aaf->msg); return 0; }
diff --git a/configure.ac b/configure.ac index 1eb9d65..d300ef0 100644 --- a/configure.ac +++ b/configure.ac @@ -181,7 +181,7 @@ AC_ARG_ENABLE([aaf],
if test "x$enable_aaf" != "xno"; then PKG_CHECK_MODULES(AVTP, avtp >= 0.1, [HAVE_AAF=yes], [HAVE_AAF=no]) - AC_CHECK_HEADERS([linux/if_ether.h linux/if_packet.h], [], [HAVE_AAF=no]) + AC_CHECK_HEADERS([linux/if_ether.h linux/if_packet.h linux/net_tstamp.h], [], [HAVE_AAF=no]) fi AM_CONDITIONAL(HAVE_AAF, test x$HAVE_AAF = xyes)
diff --git a/doc/aaf.txt b/doc/aaf.txt index e12a6f6..7bf3671 100644 --- a/doc/aaf.txt +++ b/doc/aaf.txt @@ -56,8 +56,8 @@ Synchronize system clock with PTP clock:
The commands above should be run on both AVTP Talker and Listener hosts.
-FQTSS Setup ------------ +Traffic Control Setup +---------------------
The Linux Traffic Control system provides the mqprio and cbs qdiscs which enable FQTSS on Linux. Below we provide an example to configure those qdiscs in @@ -69,18 +69,27 @@ tc-cbs(8) man pages. On the host that will run as AVTP Talker (i.e. plugin in playback mode), run the following commands:
-Configure mpqrio qdisc (replace $HANDLE_ID by an unused handle ID): +Configure mpqrio qdisc (replace $MQPRIO_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 \ + $ tc qdisc add dev $IFNAME parent root handle $MQPRIO_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@0 1@1 2@2 hw 0
-Configure cbs qdisc: +Configure cbs qdisc (replace $CBS_HANDLE_ID by an unused handle ID):
- $ tc qdisc replace dev $IFNAME parent $HANDLE_ID:1 cbs idleslope 5760 \ + $ tc qdisc replace dev $IFNAME parent $MQPRIO_HANDLE_ID:1 \ + handle $CBS_HANDLE_ID cbs idleslope 5760 \ sendslope -994240 hicredit 9 locredit -89 offload 1
-No FQTSS configuration is required at the host running as AVTP Listener. +The plugin implements a transmission mechanism that relies on ETF qdisc so make +sure it is properly configured in the system. It could be configured many way, +below follows an example. + + $ tc qdisc add dev $IFNAME parent $CBS_HANDLE_ID:1 etf \ + clockid CLOCK_TAI delta 500000 offload + +No Traffic Control configuration is required at the host running as AVTP +Listener.
Plugin Dependencies -------------------
On Sat, 08 Dec 2018 02:55:43 +0100, Andre Guedes wrote:
Hi all,
This patch series provides some follow-up improvements to the AVTP Audio Format (AAF) plugin. The highlight of this series is the implementation of a transmission offload mechanism which improves the plugin performance considerably. Details are provided in the following paragraphs.
Currently, the AVTPDU transmission interval is controlled by the AAF plugin, in software. At every timer expiration, the plugin transmits one AVTPDU. This has some implications in terms of task scheduling. For instance, in class A streams, AVTPDUs are transmitted at every 125 us which means the application task should be scheduled-in at every 125 us. In a general purpose Linux system, such scheduling interval can be hard to cope with in the long-run.
To mitigate that issue, this series introduces a transmission offload mechanism that leverages the SO_TXTIME sockopt and ETF qdisc features recently introduced to kernel 4.19. Instead of sending one AVTPDU at every timer expiration, the plugin sends several AVTPDUs at once to the kernel, configuring their Tx time so the transmission interval is maintained. The kernel can then offload packet transmission to the hardware (if the network controller supports it), providing more transmission time accuracy. This offloading mechanism enables the application to sleep for longer times, easing on task scheduling.
To illustrate the improvements provided by this series, I ran a before-after experiment. The experiment setup consisted in 2 PCs with Intel i210 card connected back-to-back running an up-to-date Archlinux with kernel 4.19.4. I ran 'aplay' for 5 minutes on one PC and captured the AVTPDUs on the other PC. The metric under evaluation is the transmission interval and it is measured by checking the 'time_delta' information from ethernet frames captured at the receiving side. If you're interested in reproducing the experiments, let me know and I can share my helper scripts.
The table below shows the experiment outcome for a Class A, stereo, 16-bit sample, 48 kHz stream. The unit is nanoseconds.
| Mean | Stdev | Min | Max | Range |
-------+--------+---------+---------+---------+---------+ Before | 125000 | 1154 | 75311 | 172144 | 96833 | After | 125000 | 18 | 124960 | 125048 | 88 |
Before this patchset, the transmission interval mean is equal to the optimal value (Class A stream -> 125 us interval), and it is kept the same after the patchset. However, the dispersion measurements had improved considerably, meaning the system is consistently transmitting AVTPDUs at the correct interval.
Finally, to help the review process, here follows a quick summary of the patches within this series:
- PATCH 1: fixes a type in the plugin documentation.
- PATCH 2: enables the user to configure the presentation time tolerance.
- PATCH 3-5: refactor the code in order to land the offload mechanism code smoothly.
- PATCH 6-7: implement the transmission offload mechanism.
This series can also be found in my alsa-plugins tree in github [1].
Regards,
Andre
[1] https://github.com/aguedes/alsa-plugins
Andre Guedes (7): doc: Fix typo in AAF doc aaf: Add presentation time tolerance aaf: Refactor AVTPDU transmission routines aaf: Refactor AVTPDU reception routines aaf: Refactor timeout routines aaf: Tx multiple AVTPDUs per media clock tick aaf: AVTPDU transmission periodicity
Applied all patches now. Thanks.
Takashi
On Mon, 10 Dec 2018 11:22:01 +0100, Takashi Iwai wrote:
On Sat, 08 Dec 2018 02:55:43 +0100, Andre Guedes wrote:
Hi all,
This patch series provides some follow-up improvements to the AVTP Audio Format (AAF) plugin. The highlight of this series is the implementation of a transmission offload mechanism which improves the plugin performance considerably. Details are provided in the following paragraphs.
Currently, the AVTPDU transmission interval is controlled by the AAF plugin, in software. At every timer expiration, the plugin transmits one AVTPDU. This has some implications in terms of task scheduling. For instance, in class A streams, AVTPDUs are transmitted at every 125 us which means the application task should be scheduled-in at every 125 us. In a general purpose Linux system, such scheduling interval can be hard to cope with in the long-run.
To mitigate that issue, this series introduces a transmission offload mechanism that leverages the SO_TXTIME sockopt and ETF qdisc features recently introduced to kernel 4.19. Instead of sending one AVTPDU at every timer expiration, the plugin sends several AVTPDUs at once to the kernel, configuring their Tx time so the transmission interval is maintained. The kernel can then offload packet transmission to the hardware (if the network controller supports it), providing more transmission time accuracy. This offloading mechanism enables the application to sleep for longer times, easing on task scheduling.
To illustrate the improvements provided by this series, I ran a before-after experiment. The experiment setup consisted in 2 PCs with Intel i210 card connected back-to-back running an up-to-date Archlinux with kernel 4.19.4. I ran 'aplay' for 5 minutes on one PC and captured the AVTPDUs on the other PC. The metric under evaluation is the transmission interval and it is measured by checking the 'time_delta' information from ethernet frames captured at the receiving side. If you're interested in reproducing the experiments, let me know and I can share my helper scripts.
The table below shows the experiment outcome for a Class A, stereo, 16-bit sample, 48 kHz stream. The unit is nanoseconds.
| Mean | Stdev | Min | Max | Range |
-------+--------+---------+---------+---------+---------+ Before | 125000 | 1154 | 75311 | 172144 | 96833 | After | 125000 | 18 | 124960 | 125048 | 88 |
Before this patchset, the transmission interval mean is equal to the optimal value (Class A stream -> 125 us interval), and it is kept the same after the patchset. However, the dispersion measurements had improved considerably, meaning the system is consistently transmitting AVTPDUs at the correct interval.
Finally, to help the review process, here follows a quick summary of the patches within this series:
- PATCH 1: fixes a type in the plugin documentation.
- PATCH 2: enables the user to configure the presentation time tolerance.
- PATCH 3-5: refactor the code in order to land the offload mechanism code smoothly.
- PATCH 6-7: implement the transmission offload mechanism.
This series can also be found in my alsa-plugins tree in github [1].
Regards,
Andre
[1] https://github.com/aguedes/alsa-plugins
Andre Guedes (7): doc: Fix typo in AAF doc aaf: Add presentation time tolerance aaf: Refactor AVTPDU transmission routines aaf: Refactor AVTPDU reception routines aaf: Refactor timeout routines aaf: Tx multiple AVTPDUs per media clock tick aaf: AVTPDU transmission periodicity
Applied all patches now. Thanks.
BTW, while building libavtp for 32bit x86, the unit test failed: The build log is found at https://build.opensuse.org/package/live_build_log/home:tiwai/libavtp/openSUS...
x86_64 seems working, so it must be specific to 32bit arch.
Takashi
Hi Takashi,
On Dec 10, 2018, at 2:59 AM, Takashi Iwai <tiwai@suse.demailto:tiwai@suse.de> wrote:
BTW, while building libavtp for 32bit x86, the unit test failed: The build log is found at https://build.opensuse.org/package/live_build_log/home:tiwai/libavtp/openSUS...
x86_64 seems working, so it must be specific to 32bit arch.
Thanks for the report. I opened an issue in libavtp’s GitHub and we’ll fix it soon.
https://github.com/AVnu/libavtp/issues/11
Regards,
Andre
participants (3)
-
Andre Guedes
-
Guedes, Andre
-
Takashi Iwai