initial commit
This commit is contained in:
@@ -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 Audinate’s 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 AUDINATE’S 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.
|
||||
//
|
||||
@@ -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 Audinate’s 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 AUDINATE’S 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.
|
||||
//
|
||||
Reference in New Issue
Block a user