initial commit

This commit is contained in:
2025-12-12 16:27:31 +01:00
commit 23c206623e
148 changed files with 42495 additions and 0 deletions

View File

@@ -0,0 +1,190 @@
//
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
//
//
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
// Software, provided always that the following conditions are met:
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
// or be used in a product, solution or offering which requires the use of another licensed Audinate
// product, solution or offering. The Software is not for use as a standalone product without any
// reference to Audinates products;
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
// or expectation of performance, compatibility, support, updates or security; and
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
// solely in the form of machine-executable object code generated by the source language processor.
//
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT.
//
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
//
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATES OPTION) TO ONE OF THE
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
// 4.2. THE REPAIR OF THE SOFTWARE;
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
//
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
// the trade names, or product names of Audinate.
//
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
// liability.
//
// AlsaDevice.hpp
// ALSA device handling
//
#pragma once
#include "alsa/asoundlib.h"
#include <string>
#include <vector>
#include <memory>
#include <inttypes.h>
#define PERIOD_FRAMES 96
#define BUFFER_PERIODS 4
#define DEFAULT_SAMPLERATE 48000
#define DEFAULT_CHANNELS 2
// in order of prefence
template <typename T>
class RingBufferT;
struct AlsaTimestamps {
uint64_t host; // Get "now" hi-res timestamp from a PCM status container.
uint64_t audio; // Get "now" hi-res audio timestamp from a PCM status container.
uint64_t trigger; // Time when stream was started or stopped
uint32_t avail_frames;
uint32_t delay_frames;
};
struct CommonParam {
int samplerate = DEFAULT_SAMPLERATE;
snd_pcm_format_t format = SND_PCM_FORMAT_UNKNOWN;
snd_pcm_uframes_t framesPerPeriod = PERIOD_FRAMES;
snd_pcm_uframes_t bufferPeriods = BUFFER_PERIODS;
};
struct StreamParam {
uint32_t channels = DEFAULT_CHANNELS;
};
class AlsaDevice {
public:
AlsaDevice(std::string);
~AlsaDevice();
int32_t getCaptureFramesReady();
int32_t getPlaybackFramesReady();
int32_t writeFrames(std::vector<RingBufferT<int32_t>> & ringRx);
int32_t writeFrames(char* buf, uint32_t frames);
int32_t readFrames(std::vector<RingBufferT<int32_t>> & ringTx);
int32_t readFrames(char* buf, uint32_t frames);
int start(uint32_t maxPlaybackChannels, uint32_t maxCaptureChannels);
void stop();
uint32_t getSamplerate();
void setSamplerate(uint32_t samplerate)
{
mCommonParam.samplerate = samplerate;
}
uint32_t getChannels(snd_pcm_stream_t dir);
uint32_t getBytesPerSample();
void setFramesPerPeriod(uint32_t frames)
{
mCommonParam.framesPerPeriod = (snd_pcm_uframes_t) frames;
}
void setBufferPeriods(uint32_t periods)
{
mCommonParam.bufferPeriods = (snd_pcm_uframes_t) periods;
}
void setDantePlaybackChannelStartIndex(uint32_t dantePlaybackChannelStartIndex)
{
mDantePlaybackChannelStartIndex = dantePlaybackChannelStartIndex;
}
void setDanteCaptureChannelStartIndex(uint32_t danteCaptureChannelStartIndex)
{
mDanteCaptureChannelStartIndex = danteCaptureChannelStartIndex;
}
void getTimestamps(AlsaTimestamps &ts, snd_pcm_stream_t dir);
int32_t getTxChannels() { return mAlsaTxChannels; }
int32_t getRxChannels() { return mAlsaRxChannels; }
bool isHWTimeStampOn(snd_pcm_t *pcmHandle);
private:
int updateHwParams();
void setHwParams(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hw_param,
StreamParam streamParam);
void setSwParams(snd_pcm_t *handle);
uint64_t alsaTimestamp2Ns(snd_htimestamp_t t);
void setFormat(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hw_param);
snd_pcm_t *mPlaybackHandle = nullptr;
snd_pcm_t *mCaptureHandle = nullptr;
snd_pcm_hw_params_t *mPlaybackHwParams = nullptr;
snd_pcm_hw_params_t *mCaptureHwParams = nullptr;
int mError = 0;
std::string mAlsaDeviceName;
CommonParam mCommonParam;
StreamParam mPlaybackParam;
StreamParam mCaptureParam;
uint32_t mAlsaRxChannels = 0;
uint32_t mAlsaTxChannels = 0;
uint32_t mAlsaBytesPerSample = 0;
bool mFirstRead = true;
bool mWriteStarted = false;
uint32_t mDanteCaptureChannelStartIndex = 0;
uint32_t mDantePlaybackChannelStartIndex = 0;
};
class AlsaAggregatedDevice {
public:
AlsaAggregatedDevice(std::string);
int32_t writeFrames(std::vector<RingBufferT<int32_t>> & ringRx);
int32_t readFrames(std::vector<RingBufferT<int32_t>> & ringTx);
int start(uint32_t maxPlaybackChannels, uint32_t maxCaptureChannels);
void stop();
uint32_t getSamplerate();
void setSamplerate(uint32_t samplerate);
uint32_t getChannels(snd_pcm_stream_t dir);
void setFramesPerPeriod(uint32_t frames);
void setBufferPeriods(uint32_t periods);
uint32_t getBytesPerSample();
private:
std::vector<std::shared_ptr<AlsaDevice>> mAlsaDevices;
std::vector<std::shared_ptr<AlsaDevice>> mAlsaStartedDevices;
};
//
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
//

View File

@@ -0,0 +1,828 @@
//
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
//
//
// 1. Subject to the terms and conditions of this Licence, Audinate hereby grants you a worldwide, non-exclusive,
// no-charge, royalty free licence to copy, modify, merge, publish, redistribute, sublicense, and/or sell the
// Software, provided always that the following conditions are met:
// 1.1. the Software must accompany, or be incorporated in a licensed Audinate product, solution or offering
// or be used in a product, solution or offering which requires the use of another licensed Audinate
// product, solution or offering. The Software is not for use as a standalone product without any
// reference to Audinates products;
// 1.2. the Software is provided as part of example code and as guidance material only without any warranty
// or expectation of performance, compatibility, support, updates or security; and
// 1.3. the above copyright notice and this License must be included in all copies or substantial portions
// of the Software, and all derivative works of the Software, unless the copies or derivative works are
// solely in the form of machine-executable object code generated by the source language processor.
//
// 2. TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT.
//
// 3. TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL AUDINATE BE LIABLE ON ANY LEGAL THEORY
// (INCLUDING, WITHOUT LIMITATION, IN AN ACTION FOR BREACH OF CONTRACT, NEGLIGENCE OR OTHERWISE) FOR ANY CLAIM,
// LOSS, DAMAGES OR OTHER LIABILITY HOWSOEVER INCURRED. WITHOUT LIMITING THE SCOPE OF THE PREVIOUS SENTENCE THE
// EXCLUSION OF LIABILITY SHALL INCLUDE: LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR CORRUPTION OF
// DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS, OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC
// LOSS; OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES, ARISING OUT OF OR
// IN CONNECTION WITH THIS AGREEMENT, ACCESS OF THE SOFTWARE OR ANY OTHER DEALINGS WITH THE SOFTWARE, EVEN IF
// AUDINATE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH CLAIM, LOSS, DAMAGES OR OTHER LIABILITY.
//
// 4. APPLICABLE LEGISLATION SUCH AS THE AUSTRALIAN CONSUMER LAW MAY APPLY REPRESENTATIONS, WARRANTIES, OR CONDITIONS,
// OR IMPOSE OBLIGATIONS OR LIABILITY ON AUDINATE THAT CANNOT BE EXCLUDED, RESTRICTED OR MODIFIED TO THE FULL
// EXTENT SET OUT IN THE EXPRESS TERMS OF THIS CLAUSE ABOVE "CONSUMER GUARANTEES". TO THE EXTENT THAT SUCH CONSUMER
// GUARANTEES CONTINUE TO APPLY, THEN TO THE FULL EXTENT PERMITTED BY THE APPLICABLE LEGISLATION, THE LIABILITY OF
// AUDINATE UNDER THE RELEVANT CONSUMER GUARANTEE IS LIMITED (WHERE PERMITTED AT AUDINATES OPTION) TO ONE OF THE
// FOLLOWING REMEDIES OR SUBSTANTIALLY EQUIVALENT REMEDIES:
// 4.1. THE REPLACEMENT OF THE SOFTWARE, THE SUPPLY OF EQUIVALENT SOFTWARE, OR SUPPLYING RELEVANT SERVICES AGAIN;
// 4.2. THE REPAIR OF THE SOFTWARE;
// 4.3. THE PAYMENT OF THE COST OF REPLACING THE SOFTWARE, OF ACQUIRING EQUIVALENT SOFTWARE, HAVING THE RELEVANT
// SERVICES SUPPLIED AGAIN, OR HAVING THE SOFTWARE REPAIRED.
//
// 5. This License does not grant any permissions or rights to use the trade marks (whether registered or unregistered),
// the trade names, or product names of Audinate.
//
// 6. If you choose to redistribute or sell the Software you may elect to offer support, maintenance, warranties,
// indemnities or other liability obligations or rights consistent with this License. However, you may only act on
// your own behalf and must not bind Audinate. You agree to indemnify and hold harmless Audinate, and its affiliates
// from any liability claimed or incurred by reason of your offering or accepting any additional warranty or additional
// liability.
//
// AlsaDevice.cpp
// ALSA device handling
//
#include "AlsaDevice.hpp"
#include "3rd_party/RingBuffer.hpp"
#include <array>
#include <iostream>
#include <sstream>
#undef NDEBUG
#include <cassert>
#define ALSA_FRAME_BUFFER_BYTES (4096 * 8)
extern uint32_t g_alsa_ring_size;
static const std::array<snd_pcm_format_t, 3> kSupportedFormats
{
{SND_PCM_FORMAT_S32, SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S16}
};
AlsaDevice::AlsaDevice(std::string deviceId) :
mAlsaDeviceName(deviceId)
{
}
AlsaDevice::~AlsaDevice()
{
stop();
}
int AlsaDevice::start
(
uint32_t dantePlaybackChannels,
uint32_t danteCaptureChannels
)
{
int err = 0;
if (dantePlaybackChannels > 0) {
if ((err = snd_pcm_hw_params_malloc(&mPlaybackHwParams))) {
goto done;
}
if ((err = snd_pcm_open(&mPlaybackHandle, mAlsaDeviceName.c_str(),
SND_PCM_STREAM_PLAYBACK, SND_PCM_NO_AUTO_RESAMPLE))) {
fprintf(stderr, "Alsa playback open %s error: %s\n",
mAlsaDeviceName.c_str(),
snd_strerror(err));
goto done;
}
snd_pcm_hw_params_any(mPlaybackHandle, mPlaybackHwParams);
if ((err = snd_pcm_hw_params_get_channels_max(
mPlaybackHwParams, &mPlaybackParam.channels))) {
fprintf(stderr, "cannot get playback max channel count (%s)\n",
snd_strerror(err));
assert(0);
}
if (mPlaybackParam.channels > dantePlaybackChannels) {
mPlaybackParam.channels = dantePlaybackChannels;
}
setHwParams(mPlaybackHandle, mPlaybackHwParams, mPlaybackParam);
setSwParams(mPlaybackHandle);
if (isHWTimeStampOn(mPlaybackHandle)) {
printf("Play back sound device relies on audio wallclock timestamps\n");
}
else {
printf("Play back sound device relies on audio sample counter timestamps\n");
}
if ((err = snd_pcm_prepare(mPlaybackHandle))) {
fprintf(stderr, "cannot prepare playback audio interface for use (%s)\n",
snd_strerror(err));
goto done;
}
}
if (danteCaptureChannels > 0) {
if ((err = snd_pcm_hw_params_malloc(&mCaptureHwParams))) {
goto done;
}
if ((err = snd_pcm_open(&mCaptureHandle, mAlsaDeviceName.c_str(),
SND_PCM_STREAM_CAPTURE,
SND_PCM_NO_AUTO_RESAMPLE | SND_PCM_NONBLOCK))) {
fprintf(stderr, "Alsa capture open error: %s\n", snd_strerror(err));
goto done;
}
snd_pcm_hw_params_any(mCaptureHandle, mCaptureHwParams);
if ((err = snd_pcm_hw_params_get_channels_max(
mCaptureHwParams, &mCaptureParam.channels))) {
fprintf(stderr, "cannot get capture max channel count (%s)\n",
snd_strerror(err));
assert(0);
}
if (mCaptureParam.channels > danteCaptureChannels) {
mCaptureParam.channels = danteCaptureChannels;
}
setHwParams(mCaptureHandle, mCaptureHwParams, mCaptureParam);
setSwParams(mCaptureHandle);
if (isHWTimeStampOn(mCaptureHandle)) {
printf("Capture sound device relies on audio wallclock timestamps\n");
}
else {
printf("Capture sound device relies on audio sample counter timestamps\n");
}
if ((err = snd_pcm_prepare(mCaptureHandle))) {
fprintf(stderr, "cannot prepare capture audio interface for use (%s)\n",
snd_strerror(err));
goto done;
}
}
if ( updateHwParams() >= 0) {
if (mPlaybackHwParams) {
snd_pcm_hw_params_get_channels(mPlaybackHwParams, &mAlsaTxChannels);
}
if (mCaptureHwParams) {
snd_pcm_hw_params_get_channels(mCaptureHwParams, &mAlsaRxChannels);
}
}
mAlsaBytesPerSample = getBytesPerSample();
mFirstRead = true;
mWriteStarted = false;
done:
if (err) {
stop();
}
return err;
}
void AlsaDevice::stop()
{
if (mPlaybackHwParams) {
snd_pcm_hw_params_free(mPlaybackHwParams);
mPlaybackHwParams = nullptr;
}
if (mCaptureHwParams) {
snd_pcm_hw_params_free(mCaptureHwParams);
mCaptureHwParams = nullptr;
}
if (mPlaybackHandle) {
snd_pcm_close(mPlaybackHandle);
mPlaybackHandle = nullptr;
}
if (mCaptureHandle) {
snd_pcm_close(mCaptureHandle);
mCaptureHandle = nullptr;
}
}
int32_t AlsaDevice::getCaptureFramesReady()
{
if (mCaptureHandle == NULL) {
fprintf(stderr, "%s: Not capture device handle\n", __func__);
return 0;
}
int32_t framesReady = 0;
if ((framesReady = snd_pcm_avail_update(mCaptureHandle)) < 0) {
return 0;
}
return framesReady;
}
int32_t AlsaDevice::getPlaybackFramesReady()
{
if (mPlaybackHandle == NULL) {
fprintf(stderr, "%s: Not playback device handle\n", __func__);
return 0;
}
int32_t framesReady = 0;
/* find out how much space is available for playback data */
if ((framesReady = snd_pcm_avail_update(mPlaybackHandle)) < 0) {
if (framesReady == -EPIPE) {
snd_pcm_recover(mPlaybackHandle, framesReady, 0);
return -1;
} else {
fprintf(stderr, "unknown ALSA avail update return value (%d)\n",
framesReady);
return 0;
}
}
// TODO: Why large frames ready value at startup?
framesReady = framesReady > 4096 ? 4096 : framesReady;
return framesReady;
}
int32_t AlsaDevice::writeFrames(char* buf, uint32_t frames)
{
if (mPlaybackHandle == NULL) {
fprintf(stderr, "%s: Not playback device handle\n", __func__);
return 0;
}
int32_t framesWritten = 0;
if ((framesWritten = snd_pcm_writei(mPlaybackHandle, buf, frames)) < 0) {
fprintf(stderr, "write failed (%s)\n", snd_strerror(framesWritten));
}
return framesWritten;
}
int32_t AlsaDevice::readFrames(char *buf, uint32_t frames)
{
if (mCaptureHandle == NULL) {
fprintf(stderr, "%s: Not capture device handle\n", __func__);
return 0;
}
// non-blocking for now
int32_t r = snd_pcm_readi(mCaptureHandle, buf, frames);
if (r >= 0) {
return r;
}
if (r == -EAGAIN) {
return 0;
}
switch (r) {
case -EBADFD:
fprintf(stderr, "%s: PCM not in correct state\n", __func__);
break;
case -EPIPE:
fprintf(stderr, "%s: PCM overrun\n", __func__);
break;
case -ESTRPIPE:
fprintf(stderr, "%s: PCM suspend\n", __func__);
break;
default:
fprintf(stderr, "Unknown error %s \n", strerror(r));
break;
}
snd_pcm_recover(mCaptureHandle, r, 0);
return -1;
}
int32_t AlsaDevice::writeFrames(std::vector<RingBufferT<int32_t>> & ringRx)
{
//TODO decouple ringbuffer from alsa device
// Convert from Dante audio format to ALSA format and send to
// ALSA device
int32_t txFramesReady = getPlaybackFramesReady();
if (txFramesReady) {
uint8_t frame_buf[ALSA_FRAME_BUFFER_BYTES] = {};
if (txFramesReady > ALSA_FRAME_BUFFER_BYTES) {
txFramesReady = ALSA_FRAME_BUFFER_BYTES;
}
if (ringRx[mDantePlaybackChannelStartIndex].getAvailableRead() >= g_alsa_ring_size/2 &&
mWriteStarted == false) {
mWriteStarted = true;
}
if (mWriteStarted) {
for (int i = 0; i < txFramesReady; i++) {
for (unsigned int alsaChannelIndex = 0, danteChannelIndex = mDantePlaybackChannelStartIndex; alsaChannelIndex < mAlsaTxChannels; alsaChannelIndex++, danteChannelIndex++) {
int tmp;
ringRx[danteChannelIndex].read(&tmp, 1);
if (mAlsaBytesPerSample == 2) {
((int16_t*) frame_buf)[i * mAlsaTxChannels + alsaChannelIndex] =
tmp / 32768;
} else if (mAlsaBytesPerSample == 4) {
((int32_t*) frame_buf)[i * mAlsaTxChannels + alsaChannelIndex] =
tmp;
} else {
std::cerr <<
"ALSA bytes per sample not yet supported." <<
std::endl;
}
}
}
}
if (mPlaybackHandle == NULL) {
fprintf(stderr, "%s: Not playback device handle\n", __func__);
return 0;
}
int32_t framesWritten = 0;
if ((framesWritten = snd_pcm_writei(mPlaybackHandle, frame_buf, txFramesReady)) < 0) {
fprintf(stderr, "write failed (%s)\n", snd_strerror(framesWritten));
}
return framesWritten;
}
return 0;
}
int32_t AlsaDevice::readFrames(std::vector<RingBufferT<int32_t>> & ringTx)
{
int32_t r = 0;
if (mFirstRead) {
mFirstRead = false;
// read required to trigger capture
if (mCaptureHandle) {
char tmp[32];
// non-blocking for now
r = snd_pcm_readi(mCaptureHandle, tmp, 1);
if (r < 0 && r != -EAGAIN) {
fprintf(stderr, "%d\n", r);
snd_pcm_recover(mCaptureHandle, r, 0);
}
}
}
int32_t rxFramesReady = getCaptureFramesReady();
if (rxFramesReady) {
uint8_t frame_buf[ALSA_FRAME_BUFFER_BYTES] = {};
if (rxFramesReady > ALSA_FRAME_BUFFER_BYTES) {
rxFramesReady = ALSA_FRAME_BUFFER_BYTES;
}
if (mCaptureHandle) {
// non-blocking for now
r = snd_pcm_readi(mCaptureHandle, frame_buf, rxFramesReady);
if (r < 0 && r != -EAGAIN) {
fprintf(stderr, "%d\n", r);
snd_pcm_recover(mCaptureHandle, r, 0);
}
}
int32_t tmp = 0;
for(int i = 0; i < rxFramesReady; i++) {
for (unsigned int alsaChannelIndex = 0, danteChannelIndex = mDanteCaptureChannelStartIndex; alsaChannelIndex < mAlsaRxChannels; alsaChannelIndex++, danteChannelIndex++) {
if (mAlsaBytesPerSample == 2) {
tmp = ((int16_t*)
frame_buf)[i * mAlsaRxChannels + alsaChannelIndex] * 32768;
} else if (mAlsaBytesPerSample == 4) {
tmp = ((int32_t*)
frame_buf)[i * mAlsaRxChannels + alsaChannelIndex];
} else {
std::cerr <<
"ALSA bytes per sample not yet supported." <<
std::endl;
}
ringTx[danteChannelIndex].write(&tmp, 1);
}
}
}
return r>=0?r:-1;
}
uint32_t AlsaDevice::getSamplerate()
{
snd_pcm_hw_params_t *hw_params =
(mPlaybackHwParams) ? mPlaybackHwParams : mCaptureHwParams;
uint32_t fs = 0;
int d;
if (updateHwParams() >= 0) {
snd_pcm_hw_params_get_rate(hw_params, &fs, &d);
}
return fs;
}
uint32_t AlsaDevice::getChannels(snd_pcm_stream_t dir)
{
return (dir == SND_PCM_STREAM_PLAYBACK) ? mAlsaTxChannels: mAlsaRxChannels;
}
uint32_t AlsaDevice::getBytesPerSample()
{
uint32_t bytes_per_sample = 0;
switch (mCommonParam.format) {
case SND_PCM_FORMAT_S32:
bytes_per_sample = 4;
break;
case SND_PCM_FORMAT_S24_3LE:
bytes_per_sample = 3;
break;
case SND_PCM_FORMAT_S16:
bytes_per_sample = 2;
break;
default:
break;
};
return bytes_per_sample;
}
// Make encoding common for playback and capture
int AlsaDevice::updateHwParams()
{
int err = 0;
if (mPlaybackHandle) {
err = snd_pcm_hw_params_current(mPlaybackHandle, mPlaybackHwParams);
if (err < 0) {
fprintf(
stderr,
"Broken configuration for stream: no configurations available: %s\n",
snd_strerror(err));
}
}
if (mCaptureHandle) {
err = snd_pcm_hw_params_current(mCaptureHandle, mCaptureHwParams);
if (mError < 0) {
fprintf(
stderr,
"Broken configuration for stream: no configurations available: %s\n",
snd_strerror(err));
}
}
return err;
}
void AlsaDevice::setFormat
(
snd_pcm_t *pcm_handle,
snd_pcm_hw_params_t *hw_param
)
{
if (mCommonParam.format != SND_PCM_FORMAT_UNKNOWN) {
int err;
// Format known
if ((err = snd_pcm_hw_params_set_format(pcm_handle, hw_param,
mCommonParam.format))) {
fprintf(stderr, "cannot set common sample format %s (%s)\n",
snd_pcm_format_name(mCommonParam.format), snd_strerror(err));
assert(0);
}
return;
}
for (auto format : kSupportedFormats) {
int supported =
snd_pcm_hw_params_set_format(pcm_handle, hw_param, format);
if (supported >= 0) {
// suitable format found
mCommonParam.format = format;
return;
}
}
fprintf(stderr, "Unable to find suitable format\n");
assert(0);
}
void AlsaDevice::setHwParams
(
snd_pcm_t *pcm_handle,
snd_pcm_hw_params_t *hw_param,
StreamParam streamParam
)
{
int err;
if ((err = snd_pcm_hw_params_set_access(
pcm_handle, hw_param, SND_PCM_ACCESS_RW_INTERLEAVED))) {
fprintf(stderr, "cannot set access type (%s)\n", snd_strerror(err));
assert(0);
}
setFormat(pcm_handle, hw_param);
uint32_t rate = mCommonParam.samplerate;
if ((err = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_param, &rate,
0))) {
fprintf(stderr, "cannot set sample rate (%s)\n", snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_hw_params_set_channels(pcm_handle, hw_param,
streamParam.channels))) {
fprintf(stderr, "cannot set channel count (%s)\n", snd_strerror(err));
assert(0);
}
snd_pcm_uframes_t bufSize = (snd_pcm_uframes_t)
(mCommonParam.framesPerPeriod * mCommonParam.bufferPeriods);
if ((err = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hw_param,
&bufSize))) {
fprintf(stderr, "cannot set buf size (%s)\n", snd_strerror(err));
assert(0);
}
snd_pcm_uframes_t periodSize = mCommonParam.framesPerPeriod;
if ((err = snd_pcm_hw_params_set_period_size_near(pcm_handle, hw_param,
&periodSize, 0))) {
fprintf(stderr, "cannot set period size (%s)\n", snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_hw_params(pcm_handle, hw_param)) < 0) {
fprintf(stderr, "cannot set parameters (%s)\n", snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_hw_params_get_buffer_size(hw_param,
&bufSize))) {
fprintf(stderr, "cannot get buf size (%s)\n", snd_strerror(err));
assert(0);
}
fprintf(stdout, "Actual buf size (%d)\n", bufSize);
if ((err = snd_pcm_hw_params_get_period_size(hw_param,
&periodSize, 0))) {
fprintf(stderr, "cannot get period size (%s)\n", snd_strerror(err));
assert(0);
}
fprintf(stdout, "Actual period size (%d)\n", periodSize);
if ((err = snd_pcm_hw_params_get_period_size_max(hw_param,
&periodSize, 0))) {
fprintf(stderr, "cannot get period size max(%s)\n", snd_strerror(err));
assert(0);
}
fprintf(stdout, "Actual period max size (%d)\n", periodSize);
if ((err = snd_pcm_hw_params_get_period_size_min(hw_param,
&periodSize, 0))) {
fprintf(stderr, "cannot get period size min(%s)\n", snd_strerror(err));
assert(0);
}
fprintf(stdout, "Actual period size min(%d)\n", periodSize);
}
void AlsaDevice::setSwParams(snd_pcm_t *pcm_handle)
{
int err;
if (pcm_handle == NULL) {
return;
}
snd_pcm_sw_params_t *swParams;
snd_pcm_sw_params_alloca(&swParams);
if ((err = snd_pcm_sw_params_current(pcm_handle, swParams))) {
fprintf(stderr, "cannot initialize software parameters structure (%s)\n",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_sw_params_set_avail_min(pcm_handle, swParams,
mCommonParam.framesPerPeriod))) {
fprintf(stderr, "cannot set minimum available count (%s)\n",
snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_sw_params_set_start_threshold(pcm_handle, swParams,
0U))) {
fprintf(stderr, "cannot set start mode (%s)\n", snd_strerror(err));
assert(0);
}
if ((err = snd_pcm_sw_params_set_tstamp_mode(pcm_handle, swParams,
SND_PCM_TSTAMP_ENABLE))) {
fprintf(stderr, "Unable to set tstamp mode : %s\n", snd_strerror(err));
assert(0);
}
auto clock = SND_PCM_TSTAMP_TYPE_MONOTONIC;
#ifdef DEP_TIMESTAMP
// DEP times tamp is monotonic raw, Alsa time stamp needs to be set to the same type
clock = SND_PCM_TSTAMP_TYPE_MONOTONIC_RAW;
#endif
if ((err = snd_pcm_sw_params_set_tstamp_type(
pcm_handle, swParams, clock))) {
fprintf(stderr, "Unable to set tstamp type : %s\n", snd_strerror(err));
assert(0);
}
// write sw params
if ((mError = snd_pcm_sw_params(pcm_handle, swParams)) < 0) {
fprintf(stderr, "cannot set software parameters (%s)\n",
snd_strerror(mError));
assert(0);
}
}
bool AlsaDevice::isHWTimeStampOn(snd_pcm_t *pcmHandle)
{
snd_pcm_hw_params_t *hwParams;
snd_pcm_hw_params_alloca(&hwParams);
/* get the current hwparams */
int err = snd_pcm_hw_params_current(pcmHandle, hwParams);
if (err < 0) {
printf("Unable to determine current hwparams_p: %s\n", snd_strerror(err));
assert(0);
}
if (snd_pcm_hw_params_supports_audio_wallclock_ts(hwParams)) {
return true;
}
else {
return false;
}
}
uint64_t AlsaDevice::alsaTimestamp2Ns(snd_htimestamp_t ts)
{
uint64_t ns;
ns = ts.tv_sec * 1000000000ULL;
ns += ts.tv_nsec;
return ns;
}
void AlsaDevice::getTimestamps(AlsaTimestamps &ts, snd_pcm_stream_t dir)
{
snd_pcm_status_t *status;
snd_pcm_status_alloca(&status);
snd_pcm_t *pcm_handle = (dir == SND_PCM_STREAM_PLAYBACK) ? mPlaybackHandle : mCaptureHandle;
if ((mError = snd_pcm_status(pcm_handle, status)) < 0) {
fprintf(stderr, "Stream status error: %s\n", snd_strerror(mError));
assert(0);
}
snd_htimestamp_t tmpTs;
snd_pcm_status_get_htstamp(status, &tmpTs);
ts.host = alsaTimestamp2Ns(tmpTs);
snd_pcm_status_get_audio_htstamp(status, &tmpTs);
ts.audio = alsaTimestamp2Ns(tmpTs);
snd_pcm_status_get_trigger_htstamp(status, &tmpTs);
ts.trigger = alsaTimestamp2Ns(tmpTs);
ts.avail_frames = snd_pcm_status_get_avail(status);
ts.delay_frames = snd_pcm_status_get_delay(status);
}
AlsaAggregatedDevice::AlsaAggregatedDevice(std::string devices)
{
std::istringstream issDevices(devices);
std::string device;
while(getline(issDevices, device, ';')) {
std::cout << "Binding " << device << std::endl;
mAlsaDevices.push_back(std::shared_ptr<AlsaDevice>(new AlsaDevice(device)));
}
}
int32_t AlsaAggregatedDevice::writeFrames(std::vector<RingBufferT<int32_t>> &ringRx)
{
int32_t framesWritten= 0;
for(auto p : mAlsaStartedDevices) {
framesWritten += p->writeFrames(ringRx);
}
return framesWritten;
}
int32_t AlsaAggregatedDevice::readFrames(std::vector<RingBufferT<int32_t>> & ringTx)
{
int32_t framesRead= 0;
for(auto p : mAlsaStartedDevices) {
framesRead += p->readFrames(ringTx);
}
return framesRead;
}
int AlsaAggregatedDevice::start(uint32_t dantePlaybackChannels, uint32_t danteCaptureChannels)
{
int err = 0;
uint32_t danteCaptureChannelStartIndex = 0, dantePlaybackChannelStartIndex = 0, channels = 0;
for(auto p : mAlsaDevices) {
if (!dantePlaybackChannels && !danteCaptureChannels) {
break;
}
err = p->start(dantePlaybackChannels, danteCaptureChannels);
if (err) {
break;
}
mAlsaStartedDevices.push_back(p);
p->setDanteCaptureChannelStartIndex(danteCaptureChannelStartIndex);
channels = p->getChannels(SND_PCM_STREAM_CAPTURE);
if (danteCaptureChannels < channels) {
channels = danteCaptureChannels;
}
danteCaptureChannels -= channels;
danteCaptureChannelStartIndex += channels;
p->setDantePlaybackChannelStartIndex(dantePlaybackChannelStartIndex);
channels = p->getChannels(SND_PCM_STREAM_PLAYBACK);
if (dantePlaybackChannels < channels) {
channels = dantePlaybackChannels;
}
dantePlaybackChannels -= channels;
dantePlaybackChannelStartIndex += channels;
}
return err;
}
void AlsaAggregatedDevice::stop()
{
for(auto p : mAlsaStartedDevices) {
p->stop();
}
mAlsaStartedDevices.clear();
}
uint32_t AlsaAggregatedDevice::getSamplerate()
{
uint32_t sampleRate = 0;
for(auto p : mAlsaDevices)
{
auto s = p->getSamplerate();
if (sampleRate && sampleRate != s) {
std::cerr << "Error, sample rate [" << s << "] is different from other's sample rate [" << sampleRate << "]" << std::endl;
}
sampleRate = s;
}
return sampleRate;
}
void AlsaAggregatedDevice::setSamplerate(uint32_t samplerate)
{
for(auto p : mAlsaDevices)
{
p->setSamplerate(samplerate);
}
}
uint32_t AlsaAggregatedDevice::getChannels(snd_pcm_stream_t dir)
{
int32_t channels= 0;
for(auto p : mAlsaDevices)
{
channels += p->getChannels(dir);
}
return channels;
}
void AlsaAggregatedDevice::setFramesPerPeriod(uint32_t frames)
{
for(auto p : mAlsaDevices)
{
p->setFramesPerPeriod(frames);
}
}
void AlsaAggregatedDevice::setBufferPeriods(uint32_t periods)
{
for(auto p : mAlsaDevices)
{
p->setBufferPeriods(periods);
}
}
uint32_t AlsaAggregatedDevice::getBytesPerSample()
{
uint32_t bytesPerSample = 0;
for(auto p : mAlsaStartedDevices){
auto b = p->getBytesPerSample();
if (bytesPerSample && bytesPerSample != b){
std::cerr << "Error, bytes per sample [" << b << "] is different from other's [" << bytesPerSample << "]" << std::endl;
}
bytesPerSample = b;
}
return bytesPerSample;
}
//
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
//