initial commit
This commit is contained in:
177
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/RingBuffer.hpp
vendored
Normal file
177
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/RingBuffer.hpp
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
Copyright (c) 2014, The Cinder Project
|
||||
This code is intended to be used with the Cinder C++ library, http://libcinder.org
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that
|
||||
the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and
|
||||
the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
|
||||
the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
|
||||
//! \brief Ringbuffer (aka circular buffer) data structure for use in concurrent audio scenarios.
|
||||
//!
|
||||
//! Other than minor modifications, this ringbuffer is a copy of Tim Blechmann's fine work, found as the base
|
||||
//! structure of boost::lockfree::spsc_queue (ringbuffer_base). Whereas the boost::lockfree data structures
|
||||
//! are meant for a wide range of applications / archs, this version specifically caters to audio processing.
|
||||
//!
|
||||
//! The implementation remains lock-free and thread-safe within a single write thread / single read thread context.
|
||||
//!
|
||||
//! \note \a T must be POD.
|
||||
template <typename T>
|
||||
class RingBufferT {
|
||||
public:
|
||||
//! Constructs a RingBufferT with size = 0
|
||||
RingBufferT() : mData( nullptr ), mAllocatedSize( 0 ), mWriteIndex( 0 ), mReadIndex( 0 ) {}
|
||||
//! Constructs a RingBufferT with \a count maximum elements.
|
||||
RingBufferT( size_t count ) : mAllocatedSize( 0 )
|
||||
{
|
||||
resize( count );
|
||||
}
|
||||
|
||||
RingBufferT( RingBufferT &&other )
|
||||
: mData( other.mData ), mAllocatedSize( other.mAllocatedSize ), mWriteIndex( 0 ), mReadIndex( 0 )
|
||||
{
|
||||
other.mData = nullptr;
|
||||
other.mAllocatedSize = 0;
|
||||
}
|
||||
|
||||
~RingBufferT()
|
||||
{
|
||||
if( mData )
|
||||
free( mData );
|
||||
}
|
||||
//! Resizes the container to contain \a count maximum elements. Invalidates the internal buffer and resets read / write indices to 0. \note Must be synchronized with both read and write threads.
|
||||
void resize( size_t count )
|
||||
{
|
||||
size_t allocatedSize = count + 1; // one bin is used to distinguish between the read and write indices when full.
|
||||
|
||||
if( mAllocatedSize )
|
||||
mData = (T *)::realloc( mData, allocatedSize * sizeof( T ) );
|
||||
else
|
||||
mData = (T *)::calloc( allocatedSize, sizeof( T ) );
|
||||
|
||||
mAllocatedSize = allocatedSize;
|
||||
clear();
|
||||
}
|
||||
//! Invalidates the internal buffer and resets read / write indices to 0. \note Must be synchronized with both read and write threads.
|
||||
void clear()
|
||||
{
|
||||
mWriteIndex = 0;
|
||||
mReadIndex = 0;
|
||||
}
|
||||
//! Returns the maximum number of elements.
|
||||
size_t getSize() const
|
||||
{
|
||||
return mAllocatedSize - 1;
|
||||
}
|
||||
//! Returns the number of elements available for wrtiing. \note Only safe to call from the write thread.
|
||||
size_t getAvailableWrite() const
|
||||
{
|
||||
return getAvailableWrite( mWriteIndex, mReadIndex );
|
||||
}
|
||||
//! Returns the number of elements available for wrtiing. \note Only safe to call from the read thread.
|
||||
size_t getAvailableRead() const
|
||||
{
|
||||
return getAvailableRead( mWriteIndex, mReadIndex );
|
||||
}
|
||||
|
||||
//! \brief Writes \a count elements into the internal buffer from \a array. \return `true` if all elements were successfully written, or `false` otherwise.
|
||||
//!
|
||||
//! \note only safe to call from the write thread.
|
||||
//! TODO: consider renaming this to writeAll / readAll, and having generic read / write that just does as much as it can
|
||||
bool write( const T *array, size_t count )
|
||||
{
|
||||
const size_t writeIndex = mWriteIndex.load( std::memory_order_relaxed );
|
||||
const size_t readIndex = mReadIndex.load( std::memory_order_acquire );
|
||||
|
||||
if( count > getAvailableWrite( writeIndex, readIndex ) )
|
||||
return false;
|
||||
|
||||
size_t writeIndexAfter = writeIndex + count;
|
||||
|
||||
if( writeIndex + count > mAllocatedSize ) {
|
||||
size_t countA = mAllocatedSize - writeIndex;
|
||||
size_t countB = count - countA;
|
||||
|
||||
std::memcpy( mData + writeIndex, array, countA * sizeof( T ) );
|
||||
std::memcpy( mData, array + countA, countB * sizeof( T ) );
|
||||
writeIndexAfter -= mAllocatedSize;
|
||||
}
|
||||
else {
|
||||
std::memcpy( mData + writeIndex, array, count * sizeof( T ) );
|
||||
if( writeIndexAfter == mAllocatedSize )
|
||||
writeIndexAfter = 0;
|
||||
}
|
||||
|
||||
mWriteIndex.store( writeIndexAfter, std::memory_order_release );
|
||||
return true;
|
||||
}
|
||||
//! \brief Reads \a count elements from the internal buffer into \a array. \return `true` if all elements were successfully read, or `false` otherwise.
|
||||
//!
|
||||
//! \note only safe to call from the read thread.
|
||||
bool read( T *array, size_t count )
|
||||
{
|
||||
const size_t writeIndex = mWriteIndex.load( std::memory_order_acquire );
|
||||
const size_t readIndex = mReadIndex.load( std::memory_order_relaxed );
|
||||
|
||||
if( count > getAvailableRead( writeIndex, readIndex ) )
|
||||
return false;
|
||||
|
||||
size_t readIndexAfter = readIndex + count;
|
||||
|
||||
if( readIndex + count > mAllocatedSize ) {
|
||||
size_t countA = mAllocatedSize - readIndex;
|
||||
size_t countB = count - countA;
|
||||
|
||||
std::memcpy( array, mData + readIndex, countA * sizeof( T ) );
|
||||
std::memcpy( array + countA, mData, countB * sizeof( T ) );
|
||||
|
||||
readIndexAfter -= mAllocatedSize;
|
||||
}
|
||||
else {
|
||||
std::memcpy( array, mData + readIndex, count * sizeof( T ) );
|
||||
if( readIndexAfter == mAllocatedSize )
|
||||
readIndexAfter = 0;
|
||||
}
|
||||
|
||||
mReadIndex.store( readIndexAfter, std::memory_order_release );
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t getAvailableWrite( size_t writeIndex, size_t readIndex ) const
|
||||
{
|
||||
size_t result = readIndex - writeIndex - 1;
|
||||
if( writeIndex >= readIndex )
|
||||
result += mAllocatedSize;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t getAvailableRead( size_t writeIndex, size_t readIndex ) const
|
||||
{
|
||||
if( writeIndex >= readIndex )
|
||||
return writeIndex - readIndex;
|
||||
|
||||
return writeIndex + mAllocatedSize - readIndex;
|
||||
}
|
||||
|
||||
|
||||
T *mData;
|
||||
size_t mAllocatedSize;
|
||||
std::atomic<size_t> mWriteIndex, mReadIndex;
|
||||
};
|
||||
12188
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/dr_flac.h
vendored
Normal file
12188
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/dr_flac.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4749
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/dr_mp3.h
vendored
Normal file
4749
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/dr_mp3.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6434
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/dr_wav.h
vendored
Normal file
6434
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/dr_wav.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
101
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/pink.hpp
vendored
Normal file
101
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/pink.hpp
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
// Original code source: https://www.firstpr.com.au/dsp/pink-noise/phil_burk_19990905_patest_pink.c
|
||||
/*
|
||||
patest_pink.c
|
||||
|
||||
Generate Pink Noise using Gardner method.
|
||||
Optimization suggested by James McCartney uses a tree
|
||||
to select which random value to replace.
|
||||
|
||||
x x x x x x x x x x x x x x x x
|
||||
x x x x x x x x
|
||||
x x x x
|
||||
x x
|
||||
x
|
||||
|
||||
Tree is generated by counting trailing zeros in an increasing index.
|
||||
When the index is zero, no random number is selected.
|
||||
|
||||
This program uses the Portable Audio library which is under development.
|
||||
For more information see: http://www.audiomulch.com/portaudio/
|
||||
|
||||
Author: Phil Burk, http://www.softsynth.com
|
||||
|
||||
Revision History:
|
||||
|
||||
Copyleft 1999 Phil Burk - No rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
static unsigned long generate_random_number(void)
|
||||
{
|
||||
static unsigned long rand_seed = 22222;
|
||||
rand_seed = (rand_seed * 196314165) + 907633515;
|
||||
return rand_seed;
|
||||
}
|
||||
|
||||
#define PINK_MAX_RANDOM_ROWS (30)
|
||||
#define PINK_RANDOM_BITS (24)
|
||||
#define PINK_RANDOM_SHIFT ((sizeof(long)*8)-PINK_RANDOM_BITS)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
long rows[PINK_MAX_RANDOM_ROWS];
|
||||
long running_sum;
|
||||
int index;
|
||||
int index_mask;
|
||||
float scalar;
|
||||
} PinkNoise;
|
||||
|
||||
PinkNoise pink;
|
||||
|
||||
void init_pink_noise()
|
||||
{
|
||||
pink.index = 0;
|
||||
pink.index_mask = (1<<PINK_MAX_RANDOM_ROWS) - 1;
|
||||
long pmax = (PINK_MAX_RANDOM_ROWS + 1) * (1<<(PINK_RANDOM_BITS-1));
|
||||
pink.scalar = 1.0f / pmax;
|
||||
for(int i=0; i < PINK_MAX_RANDOM_ROWS; i++ ) pink.rows[i] = 0;
|
||||
pink.running_sum = 0;
|
||||
}
|
||||
|
||||
float generate_pink_noise()
|
||||
{
|
||||
long new_random;
|
||||
long sum;
|
||||
float output;
|
||||
|
||||
pink.index = (pink.index + 1) & pink.index_mask;
|
||||
|
||||
if( pink.index != 0 )
|
||||
{
|
||||
|
||||
int num_zeros = 0;
|
||||
int n = pink.index;
|
||||
while( (n & 1) == 0 )
|
||||
{
|
||||
n = n >> 1;
|
||||
num_zeros++;
|
||||
}
|
||||
|
||||
pink.running_sum -= pink.rows[num_zeros];
|
||||
new_random = ((long)generate_random_number()) >> PINK_RANDOM_SHIFT;
|
||||
pink.running_sum += new_random;
|
||||
pink.rows[num_zeros] = new_random;
|
||||
}
|
||||
|
||||
new_random = ((long)generate_random_number()) >> PINK_RANDOM_SHIFT;
|
||||
sum = pink.running_sum + new_random;
|
||||
|
||||
output = pink.scalar * sum;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void get_pink_samples(double time, double timedelta, unsigned int num_samples, double frequency, double amplitude, int32_t* sampleBuffer, uint16_t bits_per_sample, double max_encoding_value)
|
||||
{
|
||||
for(unsigned int n=0; n < num_samples; n++)
|
||||
{
|
||||
sampleBuffer[n] = (int32_t)(max_encoding_value * generate_pink_noise() * amplitude);
|
||||
}
|
||||
}
|
||||
15
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/sinewave.hpp
vendored
Normal file
15
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/sinewave.hpp
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
//This code is based on examples from http://faculty.fiu.edu/~wgillam/wavfiles.html
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <math.h>
|
||||
|
||||
void get_tone_samples(double time, double timedelta, unsigned int num_samples, double frequency, double amplitude, int32_t* sampleBuffer, uint16_t bits_per_sample, double max_encoding_val)
|
||||
{
|
||||
for(unsigned int n=0; n < num_samples; n++)
|
||||
{
|
||||
time += timedelta;
|
||||
double s = sin(frequency*time*2*M_PI);
|
||||
sampleBuffer[n] = (int32_t)(max_encoding_val * s * amplitude);
|
||||
}
|
||||
}
|
||||
115
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/wav.hpp
vendored
Normal file
115
dep_examples-1.2.2.1_aarch64_Linux/dep_examples/source/3p_include/3rd_party/wav.hpp
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
//This code was based on examples from http://faculty.fiu.edu/~wgillam/wavfiles.html
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#define GET_BYTE(data, i) (((data) >> (8*(i))) & 0xFF)
|
||||
#define WAV_HEADER_SIZE 44
|
||||
|
||||
struct wav_info {
|
||||
uint16_t num_channels; /* 1 for mono, 2 for stereo, etc. */
|
||||
uint16_t bits_per_sample; /* 16 for CD, 24 for high-res, etc. */
|
||||
uint32_t sample_rate; /* 44100 for CD, 88200, 96000, 192000, etc. */
|
||||
uint32_t num_samples; /* total number of samples per channel */
|
||||
};
|
||||
|
||||
// open the output wav file and move the file pointer to the end of where the header will be, ready for writing samples
|
||||
FILE* open_wav_file(const char * filename) {
|
||||
FILE* fp = fopen(filename, "wb");
|
||||
if (fp != NULL && fseek(fp, WAV_HEADER_SIZE, SEEK_SET) == -1) {
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
void write_wav_hdr(const struct wav_info* w, FILE* fp)
|
||||
/* Write a standard 44-byte PCM format RIFF/WAVE header to the beginning of *fp.
|
||||
Again, the seek position of *fp will be left at the beginning of
|
||||
the data section, so one can immediately begin writing samples */
|
||||
{
|
||||
// We'll need the following:
|
||||
uint32_t data_size = (w->num_samples)*(w->num_channels)*(w->bits_per_sample)/8;
|
||||
uint32_t RCS = data_size+36;
|
||||
uint32_t byte_rate = (w->sample_rate)*(w->num_channels)*(w->bits_per_sample)/8;
|
||||
uint16_t block_align = (w->num_channels)*(w->bits_per_sample)/8;
|
||||
|
||||
// Prepare a standard 44 byte WAVE header from the info in w
|
||||
uint8_t h[WAV_HEADER_SIZE];
|
||||
// Bytes 1-4 are the ASCII codes for the four characters "RIFF"
|
||||
h[0]='R';
|
||||
h[1]='I';
|
||||
h[2]='F';
|
||||
h[3]='F';
|
||||
// Bytes 5-8 are RCS (i.e. data_size plus the remaining 36 bytes in the header)
|
||||
// in Little Endian format
|
||||
for(int i=0; i<4; i++) h[4+i] = GET_BYTE(RCS, i);
|
||||
// Bytes 9-12 are the ASCII codes for the four characters "WAVE"
|
||||
h[8]='W';
|
||||
h[9]='A';
|
||||
h[10]='V';
|
||||
h[11]='E';
|
||||
// Bytes 13-16 are the ASCII codes for the four characters "fmt "
|
||||
h[12]='f';
|
||||
h[13]='m';
|
||||
h[14]='t';
|
||||
h[15]= ' ';
|
||||
// Bytes 17-20 are the integer 16 (the size of the "fmt " subchunk
|
||||
// in the RIFF header we are writing) as a four-byte integer in
|
||||
// Little Endian format
|
||||
h[16]=0x10;
|
||||
h[17]=0x00;
|
||||
h[18]=0x00;
|
||||
h[19]=0x00;
|
||||
// Bytes 21-22 are the integer 1 (to indicate PCM format),
|
||||
// written as a two-byte Little Endian
|
||||
h[20]=0x01;
|
||||
h[21]=0x00;
|
||||
// Bytes 23-24 are num_channels as a two-byte Little Endian
|
||||
for(int j=0; j<2; j++) h[22+j] = GET_BYTE(w->num_channels, j);
|
||||
// Bytes 25-26 are sample_rate as a four-byte L.E.
|
||||
for(int i=0; i<4; i++) h[24+i] = GET_BYTE(w->sample_rate, i);
|
||||
// Bytes 27-30 are byte_rate as a four-byte L.E.
|
||||
for(int i=0; i<4; i++) h[28+i] = GET_BYTE(byte_rate, i);
|
||||
// Bytes 31-34 are block_align as a two-byte L.E.
|
||||
for(int j=0; j<2; j++) h[32+j] = GET_BYTE(block_align, j);
|
||||
// Bytes 35-36 are bits_per_sample as a two-byte L.E.
|
||||
for(int j=0; j<2; j++) h[34+j] = GET_BYTE(w->bits_per_sample, j);
|
||||
// Bytes 37-40 are the ASCII codes for the four characters "data"
|
||||
h[36]='d';
|
||||
h[37]='a';
|
||||
h[38]='t';
|
||||
h[39]='a';
|
||||
// Bytes 41-44 are data_size as a four-byte L.E.
|
||||
for(int i=0; i<4; i++) h[40+i] = GET_BYTE(data_size, i);
|
||||
|
||||
// Write the header to the beginning of *fp
|
||||
fseek(fp,0,SEEK_SET);
|
||||
fwrite(h,1,WAV_HEADER_SIZE,fp);
|
||||
}
|
||||
|
||||
|
||||
void write_sample(const struct wav_info* w, FILE* fp, const int32_t* sample)
|
||||
/* Write a sample to *fp in the correct Little Endian format.
|
||||
sample should be an array with w->num_channels elements.
|
||||
Note that we use the int_fast32_t datatype to hold samples, which should be
|
||||
changed if you want to use bits_per_sample > 32. Also, if you use, say,
|
||||
bits_per_sample=24, then you want to make sure that your actual samples
|
||||
are going to fit into a 3-byte Little Endian integer in twos-complement
|
||||
encoding. If you're only going to use bits_per_sample=16, you could
|
||||
use int_fast16_t instead of int_fast32_t. Note also that the WAVE
|
||||
file format expects 8-bit samples to be UNsigned, so if you're going
|
||||
to use bits_per_sample=8, then you should use uint_fast8_t to hold
|
||||
your samples. */
|
||||
|
||||
{
|
||||
// We'll assume w->bits_per_sample is divisible by 8, otherwise
|
||||
// one should do bytes_per_sample++ and make sure
|
||||
// the last (w->bits_per_sample % 8) bits of each sample[i] are zero
|
||||
int b = w->bits_per_sample/8; // bytes per sample
|
||||
uint8_t x[b];
|
||||
for(unsigned int i=0; i<w->num_channels; i++) {
|
||||
// populate x with sample[i] in Little Endian format, then write it
|
||||
for(int j=0; j<b; j++) x[j] = GET_BYTE(sample[i], j);
|
||||
fwrite(x,1,b,fp);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
//
|
||||
@@ -0,0 +1,158 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(dep_example_apps)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
|
||||
# 3rd party libraries
|
||||
add_library(dep_example_third_party_includes INTERFACE)
|
||||
target_include_directories(dep_example_third_party_includes INTERFACE 3p_include/)
|
||||
|
||||
# versions
|
||||
add_library(dep_example_version STATIC
|
||||
version/versions.c)
|
||||
target_include_directories(dep_example_version PUBLIC version/)
|
||||
|
||||
############################################################
|
||||
# Loopback tool
|
||||
############################################################
|
||||
|
||||
add_executable(dep_example_loopback
|
||||
dep_loopback/DanteLoopback.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(dep_example_loopback
|
||||
dep_audio
|
||||
dep_example_version
|
||||
)
|
||||
set_target_properties(dep_example_loopback PROPERTIES OUTPUT_NAME DepLoopback)
|
||||
|
||||
############################################################
|
||||
# Timing diagnostic tool
|
||||
############################################################
|
||||
|
||||
add_executable(dep_example_timing
|
||||
dep_timing_trace/DanteTimingTrace.cpp
|
||||
)
|
||||
target_link_libraries(dep_example_timing
|
||||
dep_audio
|
||||
dep_example_version
|
||||
)
|
||||
set_target_properties(dep_example_timing PROPERTIES OUTPUT_NAME DepTiming)
|
||||
|
||||
# Linux examples
|
||||
if(DANTE_LINUX_EXAMPLES)
|
||||
|
||||
############################################################
|
||||
# Alsa Device Library
|
||||
############################################################
|
||||
|
||||
add_library(dep_example_alsa_device STATIC
|
||||
AlsaDevice/src/AlsaDevice.cpp
|
||||
)
|
||||
target_include_directories(dep_example_alsa_device PUBLIC
|
||||
AlsaDevice/include
|
||||
)
|
||||
target_link_libraries(dep_example_alsa_device PUBLIC
|
||||
alsalib
|
||||
dep_example_third_party_includes
|
||||
)
|
||||
|
||||
############################################################
|
||||
# Metadata Header Tool
|
||||
############################################################
|
||||
|
||||
add_executable(dep_example_info
|
||||
dep_info/dinfo.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(dep_example_info
|
||||
dep_audio
|
||||
dep_example_version
|
||||
)
|
||||
set_target_properties(dep_example_info PROPERTIES OUTPUT_NAME dinfo)
|
||||
|
||||
############################################################
|
||||
# Playback Tool
|
||||
############################################################
|
||||
|
||||
add_executable(dep_example_playback
|
||||
dep_play/dplay.cpp
|
||||
)
|
||||
target_link_libraries(dep_example_playback
|
||||
dep_audio
|
||||
dep_example_third_party_includes
|
||||
dep_example_version
|
||||
)
|
||||
set_target_properties(dep_example_playback PROPERTIES OUTPUT_NAME dplay)
|
||||
|
||||
############################################################
|
||||
# Recording Tool
|
||||
############################################################
|
||||
|
||||
add_executable(dep_example_record
|
||||
dep_record/drecord.cpp
|
||||
)
|
||||
target_link_libraries(dep_example_record
|
||||
dep_audio
|
||||
dep_example_third_party_includes
|
||||
dep_example_version
|
||||
)
|
||||
set_target_properties(dep_example_record PROPERTIES OUTPUT_NAME drecord)
|
||||
|
||||
############################################################
|
||||
# Tone/Noise Generation Tool
|
||||
############################################################
|
||||
|
||||
add_executable(dep_example_soundtest
|
||||
dep_test/dtest.cpp
|
||||
)
|
||||
target_link_libraries(dep_example_soundtest
|
||||
dep_audio
|
||||
dep_example_third_party_includes
|
||||
dep_example_version
|
||||
)
|
||||
set_target_properties(dep_example_soundtest PROPERTIES OUTPUT_NAME dtest)
|
||||
|
||||
############################################################
|
||||
# Alsa Audio Input/Output Tool
|
||||
############################################################
|
||||
|
||||
add_executable(dep_example_alsa_bridge
|
||||
dep_alsa_bridge/DanteAlsaBridge.cpp
|
||||
)
|
||||
target_link_libraries(dep_example_alsa_bridge
|
||||
dep_audio
|
||||
dep_example_alsa_device
|
||||
dep_example_third_party_includes
|
||||
dep_example_version
|
||||
)
|
||||
set_target_properties(dep_example_alsa_bridge PROPERTIES OUTPUT_NAME DepAlsaBridge)
|
||||
|
||||
############################################################
|
||||
# Dante Soundcard Tool
|
||||
############################################################
|
||||
|
||||
set(SOUNDCARD_SHARED_OBJECT_NAME "dsoundcard.so")
|
||||
set(SOUNDCARD_SHARED_OBJECT_NAME ${SOUNDCARD_SHARED_OBJECT_NAME} PARENT_SCOPE)
|
||||
add_executable(dep_example_soundcard
|
||||
dep_soundcard/src/dsoundcard.cpp
|
||||
dep_soundcard/src/dsoundcard_intermediatebuffer.cpp
|
||||
dep_soundcard/src/dsoundcard_logging.cpp
|
||||
dep_soundcard/src/alsa_plugin.cpp
|
||||
)
|
||||
target_link_libraries(dep_example_soundcard
|
||||
dep_audio
|
||||
dep_example_alsa_device
|
||||
dep_example_third_party_includes
|
||||
dep_example_version
|
||||
)
|
||||
target_include_directories(dep_example_soundcard PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/dep_soundcard/include
|
||||
)
|
||||
set_target_properties(dep_example_soundcard PROPERTIES OUTPUT_NAME ${SOUNDCARD_SHARED_OBJECT_NAME})
|
||||
target_compile_options(dep_example_soundcard PUBLIC -fPIC -DPIC)
|
||||
target_link_options(dep_example_soundcard PUBLIC -shared -fPIC -DPIC)
|
||||
|
||||
|
||||
|
||||
endif()
|
||||
@@ -0,0 +1,52 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include(ExternalProject)
|
||||
include(FetchContent)
|
||||
|
||||
set(TARGET alsalib)
|
||||
|
||||
# If ALSA_INSTALL_PATH is set it should point to an externally provided ALSA installation.
|
||||
# Otherwise a default version will be built.
|
||||
if (ALSA_INSTALL_PATH)
|
||||
add_library(alsalib SHARED IMPORTED GLOBAL)
|
||||
set_target_properties(alsalib PROPERTIES
|
||||
IMPORTED_LOCATION ${ALSA_INSTALL_PATH}/lib/libasound.so
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${ALSA_INSTALL_PATH}/include
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(EP_TARGET ${TARGET}_source)
|
||||
|
||||
FetchContent_Declare(alsalib_download
|
||||
URL https://www.alsa-project.org/files/pub/lib/alsa-lib-1.0.29.tar.bz2
|
||||
)
|
||||
|
||||
FetchContent_GetProperties(alsalib_download)
|
||||
if (NOT alsalib_download_POPULATED)
|
||||
FetchContent_Populate(alsalib_download)
|
||||
endif()
|
||||
|
||||
set(EP_INSTALL_DIR ${alsalib_download_SOURCE_DIR}/staging)
|
||||
|
||||
ExternalProject_Add(alsalib_build
|
||||
SOURCE_DIR ${alsalib_download_SOURCE_DIR}
|
||||
BINARY_DIR ${alsalib_download_BINARY_DIR}
|
||||
|
||||
CONFIGURE_COMMAND ""
|
||||
|
||||
BUILD_COMMAND CC=${CMAKE_C_COMPILER} ${alsalib_download_SOURCE_DIR}/configure --prefix=${EP_INSTALL_DIR} --host=${TOOLCHAIN_TARGET} --disable-python
|
||||
|
||||
COMMAND make
|
||||
)
|
||||
|
||||
# CMake known bug - INTERFACE_INCLUDE_DIRECTORIES requires dir to exist at config stage
|
||||
file(MAKE_DIRECTORY ${EP_INSTALL_DIR}/include)
|
||||
|
||||
add_library(${TARGET} SHARED IMPORTED GLOBAL)
|
||||
set_target_properties(${TARGET} PROPERTIES
|
||||
IMPORTED_LOCATION ${EP_INSTALL_DIR}/lib/libasound.so
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${EP_INSTALL_DIR}/include
|
||||
)
|
||||
add_dependencies(${TARGET} ${EP_TARGET})
|
||||
|
||||
@@ -0,0 +1,428 @@
|
||||
//
|
||||
// Copyright © 2021 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.
|
||||
//
|
||||
// DanteAlsa.cpp
|
||||
// Dante-ALSA bridge
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
#include "dante/Priority.hpp"
|
||||
#include <assert.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "AlsaDevice.hpp"
|
||||
#include "3rd_party/RingBuffer.hpp"
|
||||
#include "versions.h"
|
||||
#include <queue>
|
||||
|
||||
#if TRACEPOINTS
|
||||
#include <sys/sdt.h>
|
||||
#endif
|
||||
|
||||
static bool g_running = true;
|
||||
|
||||
static std::string g_soundcard = "hw:1,0";
|
||||
static int32_t g_starting_dante_rx = 0;
|
||||
static int32_t g_starting_dante_tx = 0;
|
||||
uint32_t g_alsa_ring_size = 96;
|
||||
|
||||
#define ALSA_DEFAULT_FRAMES_PER_PERIOD 24
|
||||
#define ALSA_DEFAULT_BUFFER_PERIODS 4
|
||||
|
||||
static void signal_handler(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
g_running = false;
|
||||
signal(SIGINT, signal_handler);
|
||||
}
|
||||
|
||||
class DanteAlsaBridge {
|
||||
public:
|
||||
DanteAlsaBridge(Dante::Buffers &buffers, int txLatencySamples) :
|
||||
mBuffers(buffers), mTxLatencySamples(txLatencySamples),
|
||||
mSamplesPerPeriod(), mSamplesPerChannel(), mDanteTxHeadSamples(),
|
||||
mDanteRxHeadSamples(), mDanteTxChannels(), mDanteRxChannels(),
|
||||
mAlsaTxChannels(0), mAlsaRxChannels(0),
|
||||
mAlsaFramesPerPeriod(ALSA_DEFAULT_FRAMES_PER_PERIOD),
|
||||
mAlsaBufferPeriods(ALSA_DEFAULT_BUFFER_PERIODS)
|
||||
{
|
||||
mAlsaDevice = std::unique_ptr<AlsaAggregatedDevice>(new AlsaAggregatedDevice(g_soundcard));
|
||||
}
|
||||
|
||||
~DanteAlsaBridge() {}
|
||||
|
||||
void setAlsaFramesPerPeriod(uint32_t frames)
|
||||
{
|
||||
mAlsaFramesPerPeriod = frames;
|
||||
}
|
||||
|
||||
void setAlsaBufferPeriods(uint32_t periods)
|
||||
{
|
||||
mAlsaBufferPeriods = periods;
|
||||
}
|
||||
|
||||
int start()
|
||||
{
|
||||
auto metadata = mBuffers.getHeader();
|
||||
mAlsaDevice->setFramesPerPeriod(mAlsaFramesPerPeriod);
|
||||
mAlsaDevice->setBufferPeriods(mAlsaBufferPeriods);
|
||||
mAlsaDevice->setSamplerate(metadata->audio.sample_rate);
|
||||
|
||||
int result =
|
||||
mAlsaDevice->start
|
||||
(
|
||||
metadata->audio.num_rx_channels,
|
||||
metadata->audio.num_tx_channels
|
||||
);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
mAlsaTxChannels = mAlsaDevice->getChannels(SND_PCM_STREAM_PLAYBACK);
|
||||
mAlsaRxChannels = mAlsaDevice->getChannels(SND_PCM_STREAM_CAPTURE);
|
||||
mAlsaBytesPerSample = mAlsaDevice->getBytesPerSample();
|
||||
|
||||
if (mTxLatencySamples == 0) {
|
||||
// Not set explicitly on command line. Default is approx 1ms
|
||||
// at the current sample rate.
|
||||
mTxLatencySamples = metadata->audio.sample_rate / 1000;
|
||||
}
|
||||
|
||||
std::cout << "Started soundcard: " << std::endl;
|
||||
std::cout << "\tChannels: " << mAlsaRxChannels << " RX, " <<
|
||||
mAlsaTxChannels << " TX" << std::endl;
|
||||
std::cout << "\tSample rate: " << metadata->audio.sample_rate <<
|
||||
std::endl;
|
||||
std::cout << "\tFrames per period: " << mAlsaFramesPerPeriod <<
|
||||
std::endl;
|
||||
std::cout << "\tPeriods per buffer: " << mAlsaBufferPeriods <<
|
||||
std::endl;
|
||||
std::cout << "DEP interface:" << std::endl;
|
||||
std::cout << "\tTx Latency Samples: " << mTxLatencySamples <<
|
||||
std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
mAlsaDevice->stop();
|
||||
}
|
||||
|
||||
void work(unsigned int numPeriods)
|
||||
{
|
||||
alsaPlayback(numPeriods);
|
||||
alsaCapture(numPeriods);
|
||||
}
|
||||
|
||||
// Called when there are changes to the buffer header epoch or samplerate
|
||||
// values.
|
||||
void reset()
|
||||
{
|
||||
auto metadata = mBuffers.getHeader();
|
||||
mSamplesPerPeriod = metadata->time.samples_per_period;
|
||||
mSamplesPerChannel = metadata->audio.samples_per_channel;
|
||||
|
||||
mRingTx.resize(mAlsaRxChannels);
|
||||
mDanteTxChannels.resize(mAlsaRxChannels);
|
||||
for (unsigned int i = 0; i < mAlsaRxChannels; i++) {
|
||||
mRingTx[i].resize(g_alsa_ring_size);
|
||||
mDanteTxChannels[i] =
|
||||
(int32_t *)mBuffers.getDanteTxChannel(i + g_starting_dante_tx);
|
||||
}
|
||||
|
||||
mRingRx.resize(mAlsaTxChannels);
|
||||
mDanteRxChannels.resize(mAlsaTxChannels);
|
||||
for (unsigned int i = 0; i < mAlsaTxChannels; i++) {
|
||||
mRingRx[i].resize(g_alsa_ring_size);
|
||||
mDanteRxChannels[i] = (const int32_t *)
|
||||
mBuffers.getDanteRxChannel(i + g_starting_dante_rx );
|
||||
}
|
||||
|
||||
mDanteRxHeadSamples = (unsigned int)
|
||||
((metadata->time.period_count * mSamplesPerPeriod) %
|
||||
mSamplesPerChannel);
|
||||
mDanteTxHeadSamples = (unsigned int)
|
||||
((metadata->time.period_count * mSamplesPerPeriod +
|
||||
mTxLatencySamples) % mSamplesPerChannel);
|
||||
}
|
||||
|
||||
private:
|
||||
Dante::Buffers &mBuffers;
|
||||
unsigned int mTxLatencySamples;
|
||||
|
||||
unsigned int mSamplesPerPeriod;
|
||||
unsigned int mSamplesPerChannel;
|
||||
unsigned int mDanteTxHeadSamples;
|
||||
unsigned int mDanteRxHeadSamples;
|
||||
std::vector<int32_t *> mDanteTxChannels;
|
||||
std::vector<const int32_t *> mDanteRxChannels;
|
||||
std::vector<RingBufferT<int32_t>> mRingRx;
|
||||
std::vector<RingBufferT<int32_t>> mRingTx;
|
||||
|
||||
// ALSA specific state
|
||||
std::unique_ptr<AlsaAggregatedDevice> mAlsaDevice;
|
||||
uint32_t mAlsaTxChannels;
|
||||
uint32_t mAlsaRxChannels;
|
||||
uint32_t mAlsaBytesPerSample;
|
||||
uint32_t mAlsaFramesPerPeriod;
|
||||
uint32_t mAlsaBufferPeriods;
|
||||
std::queue<int32_t> fifo_errors;
|
||||
|
||||
void alsaPlayback(unsigned int numPeriods)
|
||||
{
|
||||
unsigned int numSamples = numPeriods * mSamplesPerPeriod;
|
||||
|
||||
// For extrema stalls (eg breakpoints while debugging) just
|
||||
// jump to catch up
|
||||
if (numSamples > mSamplesPerChannel) {
|
||||
numSamples = numSamples % mSamplesPerChannel;
|
||||
}
|
||||
|
||||
// Unwrap the Dante RX loop and copy data into local ring buffer.
|
||||
if (mDanteRxHeadSamples + numSamples > mSamplesPerChannel) {
|
||||
unsigned int n1 = mSamplesPerChannel - mDanteRxHeadSamples;
|
||||
unsigned int n2 = numSamples - n1;
|
||||
for (unsigned int c = 0; c < mAlsaTxChannels; c++) {
|
||||
mRingRx[c].write(mDanteRxChannels[c] + mDanteRxHeadSamples, n1);
|
||||
}
|
||||
mDanteRxHeadSamples = 0;
|
||||
for (unsigned int c = 0; c < mAlsaTxChannels; c++) {
|
||||
mRingRx[c].write(mDanteRxChannels[c] + mDanteRxHeadSamples, n2);
|
||||
}
|
||||
mDanteRxHeadSamples += n2;
|
||||
} else {
|
||||
for (unsigned int c = 0; c < mAlsaTxChannels; c++) {
|
||||
mRingRx[c].write
|
||||
(
|
||||
mDanteRxChannels[c] + mDanteRxHeadSamples, numSamples
|
||||
);
|
||||
}
|
||||
mDanteRxHeadSamples += numSamples;
|
||||
}
|
||||
|
||||
mAlsaDevice->writeFrames(mRingRx);
|
||||
}
|
||||
|
||||
void alsaCapture(unsigned int numPeriods)
|
||||
{
|
||||
unsigned int numSamples = numPeriods * mSamplesPerPeriod;
|
||||
mAlsaDevice->readFrames(mRingTx);
|
||||
|
||||
static bool started = false;
|
||||
if (mRingTx[0].getAvailableRead() > g_alsa_ring_size/2 &&
|
||||
started == false) {
|
||||
started = true;
|
||||
}
|
||||
|
||||
// Dante
|
||||
// For extrama stalls (eg breakpoints while debugging)
|
||||
// just jump to catch up
|
||||
if (numSamples > mSamplesPerChannel) {
|
||||
numSamples = numSamples % mSamplesPerChannel;
|
||||
}
|
||||
|
||||
// Unwrap the Dante TX loop
|
||||
if (mDanteTxHeadSamples + numSamples > mSamplesPerChannel) {
|
||||
unsigned int n1 = mSamplesPerChannel - mDanteTxHeadSamples;
|
||||
unsigned int n2 = numSamples - n1;
|
||||
for (unsigned int c = 0; c < mAlsaRxChannels; c++) {
|
||||
mRingTx[c].read(mDanteTxChannels[c] + mDanteTxHeadSamples, n1);
|
||||
}
|
||||
mDanteTxHeadSamples = 0;
|
||||
for (unsigned int c = 0; c < mAlsaRxChannels; c++) {
|
||||
mRingTx[c].read(mDanteTxChannels[c] + mDanteTxHeadSamples, n2);
|
||||
}
|
||||
mDanteTxHeadSamples += n2;
|
||||
} else {
|
||||
for (unsigned int c = 0; c < mAlsaRxChannels; c++) {
|
||||
mRingTx[c].read((mDanteTxChannels[c] + mDanteTxHeadSamples),
|
||||
numSamples);
|
||||
}
|
||||
mDanteTxHeadSamples += numSamples;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int err;
|
||||
int opt;
|
||||
uint32_t framesPerPeriod = 0;
|
||||
uint32_t bufferPeriods = 0;
|
||||
int txLatencySamples = 0;
|
||||
|
||||
while ((opt = getopt(argc, argv, "b:d:p:r:t:l:c:v")) != -1) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
g_soundcard = optarg;
|
||||
break;
|
||||
case 'r':
|
||||
g_starting_dante_rx = atoi(optarg);
|
||||
break;
|
||||
case 't':
|
||||
g_starting_dante_tx = atoi(optarg);
|
||||
break;
|
||||
case 'p':
|
||||
framesPerPeriod = atoi(optarg);
|
||||
break;
|
||||
case 'b':
|
||||
bufferPeriods = atoi(optarg);
|
||||
break;
|
||||
case 'l':
|
||||
txLatencySamples = atoi(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
g_alsa_ring_size = atoi(optarg);
|
||||
std::cout << "Using ring buffer size of " << g_alsa_ring_size << std::endl;
|
||||
break;
|
||||
case 'v':
|
||||
std::cout << getVersionInfo() << std::endl;
|
||||
return 0;
|
||||
default:
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout << "\t-d ALSA Device(s) e.g. 'hw:1,0' or multi devices 'hw:1,0;hw:1,1'" << std::endl;
|
||||
std::cout <<
|
||||
"\t-r Starting Dante RX channel to map - default is 0" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-t Starting Dante TX channel to map - default is 0" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-p ALSA frames per period - default is " <<
|
||||
ALSA_DEFAULT_FRAMES_PER_PERIOD <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-b ALSA periods per buffer - default is " <<
|
||||
ALSA_DEFAULT_BUFFER_PERIODS <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-l Tx Latency write-back samples - default will be " <<
|
||||
"calculated to correspond to 1ms at the configured Dante " <<
|
||||
"sample rate" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-c Ring buffer size - default is " << g_alsa_ring_size <<
|
||||
std::endl;
|
||||
std::cout << "\t-v Print out version" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Dante::Buffers buffers;
|
||||
Dante::Runner runner(buffers);
|
||||
DanteAlsaBridge bridge(buffers, txLatencySamples);
|
||||
|
||||
setDantePriority(argv[0]);
|
||||
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
if (framesPerPeriod > 0) {
|
||||
bridge.setAlsaFramesPerPeriod(framesPerPeriod);
|
||||
}
|
||||
if (bufferPeriods > 0) {
|
||||
bridge.setAlsaBufferPeriods(bufferPeriods);
|
||||
}
|
||||
|
||||
int lastConnectionResult = 0;
|
||||
while (g_running) {
|
||||
if (!lastConnectionResult)
|
||||
{
|
||||
std::cerr << "Connecting..." << std::endl;
|
||||
}
|
||||
int result = buffers.connect("DanteEP", false);
|
||||
if (result) {
|
||||
if (result != lastConnectionResult)
|
||||
{
|
||||
lastConnectionResult = result;
|
||||
std::cerr <<
|
||||
"Error connecting to shared memory: " <<
|
||||
Dante::SharedMemory::getErrorMessage(result) <<
|
||||
std::endl;
|
||||
std::cerr << "Trying to connect again..." << std::endl;
|
||||
}
|
||||
sleep(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
lastConnectionResult = 0;
|
||||
std::cerr << "Connected" << std::endl;
|
||||
|
||||
err = bridge.start();
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
auto _work = [&bridge](unsigned int numPeriods) {
|
||||
bridge.work(numPeriods);
|
||||
};
|
||||
auto _reset = [&bridge]() { bridge.reset(); };
|
||||
runner.setActiveChangedFn([](bool active) {
|
||||
std::cerr << (active ? "Activated" : "Deactivated") << std::endl;
|
||||
});
|
||||
runner.run(g_running, _work, _reset);
|
||||
|
||||
std::cerr << "Disconnecting..." << std::endl;
|
||||
bridge.stop();
|
||||
buffers.disconnect();
|
||||
std::cerr << "Disconnected" << std::endl;
|
||||
}
|
||||
|
||||
cleanupDantePriority();
|
||||
|
||||
return 0;
|
||||
}
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,44 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(DEP_audio)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
|
||||
# Single library for now
|
||||
add_library(dep_audio STATIC
|
||||
src/DanteBuffers.cpp
|
||||
src/DantePriority.cpp
|
||||
src/DanteRunner.cpp
|
||||
src/versions.c
|
||||
src/posix/DanteSharedMemory.cpp
|
||||
src/posix/DanteTiming.cpp
|
||||
)
|
||||
|
||||
target_sources(dep_audio
|
||||
PRIVATE include_private/versions.h
|
||||
)
|
||||
|
||||
target_include_directories(dep_audio PUBLIC
|
||||
include
|
||||
)
|
||||
|
||||
target_include_directories(dep_audio
|
||||
PRIVATE
|
||||
include_private
|
||||
)
|
||||
set_property(TARGET dep_audio PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
target_compile_options(dep_audio
|
||||
PRIVATE
|
||||
-Wno-error=nonnull
|
||||
-Wall
|
||||
-Werror
|
||||
-Wno-error=unknown-pragmas
|
||||
-Wno-error=unused-function
|
||||
-Wno-error=strict-aliasing
|
||||
)
|
||||
|
||||
target_compile_definitions(dep_audio
|
||||
PRIVATE NDEBUG
|
||||
)
|
||||
target_link_libraries(dep_audio
|
||||
PUBLIC pthread rt
|
||||
)
|
||||
@@ -0,0 +1,415 @@
|
||||
//
|
||||
// Copyright © 2020-2023 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.
|
||||
//
|
||||
|
||||
/// @file Buffers.hpp
|
||||
/// Audio buffers header file
|
||||
#pragma once
|
||||
|
||||
#ifdef WIN32
|
||||
#include <Windows.h>
|
||||
#include <inttypes.h>
|
||||
#endif
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#define DANTE_ENCODING__PCM32 32
|
||||
#define DANTE_ENCODING__FLOAT32 65
|
||||
|
||||
/// If set, the meta-data header, TX and RX channel buffers are in seperate memory segments.
|
||||
/// If not set, the meta-data header and channel buffers are in a single continuous memory segment.
|
||||
#define DANTE_BUFFERS_FLAG__SEPARATE_CHANNEL_MEMORY 0x1
|
||||
|
||||
/// Set by DEP when updating multiple fields in the time struct that need to be read atomically
|
||||
#define DANTE_BUFFERS_FLAG__TIMING_UPDATE_SYNC 0x10000
|
||||
|
||||
/// Special non-zero value used for header magic
|
||||
#define DANTE_BUFFERS_HDR_MAGIC 0x50525354
|
||||
|
||||
namespace Dante
|
||||
{
|
||||
|
||||
///
|
||||
/// Overlay structure for the shared memory buffer header block
|
||||
///
|
||||
typedef struct buffer_header
|
||||
{
|
||||
/// Describes the shared memory layout and state
|
||||
struct
|
||||
{
|
||||
/// Set to zero when the shared memory buffer is not yet configured or becomes invalid.
|
||||
/// Set to DANTE_BUFFERS_HDR_MAGIC when the shared memory buffer has been fully set
|
||||
/// up and is valid for use.
|
||||
uint32_t magic_marker;
|
||||
|
||||
/// Total length in bytes of the shared memory. Includes headers and audio channel buffers.
|
||||
uint32_t buffer_length;
|
||||
|
||||
/// Total length in bytes of the metadata header
|
||||
uint32_t metadata_header_length;
|
||||
|
||||
/// Flags used for various buffer operations. See DANTE_BUFFERS_FLAG defines.
|
||||
uint32_t flags;
|
||||
|
||||
/// Offset in bytes to the firxt TX channel buffer from the start of this header
|
||||
uint32_t first_tx_channel_offset_bytes;
|
||||
|
||||
/// Offset in bytes to the firxt RX channel buffer from the start of this header
|
||||
uint32_t first_rx_channel_offset_bytes;
|
||||
|
||||
/// Offset in bytes to the timing object subheader from the start of this header
|
||||
uint32_t timing_object_subheader_offset_bytes;
|
||||
|
||||
/// Incremented at the start and end of a buffer reset operation
|
||||
uint32_t reset_count;
|
||||
} metadata;
|
||||
|
||||
/// Describes the audio data stored in the channel buffers
|
||||
struct
|
||||
{
|
||||
/// When non-zero, this value is the currently configured device sample rate.
|
||||
/// When zero, it means the sample rate is in the process of being changed.
|
||||
uint32_t sample_rate;
|
||||
|
||||
/// Encoding of the audio data in the channel buffers. Currently always DANTE_ENCODING__PCM32.
|
||||
uint32_t encoding;
|
||||
|
||||
/// The total number of samples that can be stored in the buffers for each channel
|
||||
uint32_t samples_per_channel;
|
||||
|
||||
/// Total size in bytes for each channel buffer
|
||||
uint32_t bytes_per_channel;
|
||||
|
||||
/// Total number of available TX channels (minimum of configured and activated number of channels)
|
||||
uint32_t num_tx_channels;
|
||||
|
||||
/// Total number of available RX channels (minimum of configured and activated number of channels)
|
||||
uint32_t num_rx_channels;
|
||||
uint32_t pad7;
|
||||
uint32_t pad8;
|
||||
} audio;
|
||||
|
||||
/// time fields
|
||||
struct
|
||||
{
|
||||
/// The epoch represents the PTP time at which the device last achieved PTP synchronisation.
|
||||
/// To get the complete epoch time, convert @ref epoch_seconds and @ref epoch_samples into the same units
|
||||
/// and add them together. For example, to get epoch time in samples:
|
||||
/// (epoch_seconds * sample_rate) + epoch_samples
|
||||
/// @note The epoch is initially zero when the device first starts up before it has achieved
|
||||
/// PTP sync for the first time. Subsequently the epoch values are only updated upon each sync
|
||||
/// event but not on sync loss. That is, this value cannot be used to determine the
|
||||
/// device's current PTP sync state apart from the initial state (yet to sync for the first time).
|
||||
uint32_t epoch_seconds;
|
||||
|
||||
/// The sub-second portion of the epoch in unit of samples. See @ref epoch_seconds for more details.
|
||||
uint32_t epoch_samples;
|
||||
|
||||
/// This value gives the number of samples that will elapse before DEP signals to the application
|
||||
/// that there is audio data available. A lower value can allow the application to wake up
|
||||
/// more often to process audio data but with the tradeoff that CPU load will increase.
|
||||
uint32_t samples_per_period;
|
||||
|
||||
/// This value is incremented every time a period has elapsed.
|
||||
/// Applications can use this value to determine how many periods of audio data can be read/written
|
||||
/// since the last time it woke up.
|
||||
uint64_t period_count;
|
||||
|
||||
/// Currently unused
|
||||
uint32_t clock_drift_ppb;
|
||||
|
||||
/// The local monotonic time at the current period_count value.
|
||||
/// The montonic clock source is CLOCK_MONOTONIC_RAW.
|
||||
/// (monotonic, period_count) timing pair values can be used to track the rate change
|
||||
/// of the local time domain against the PTP time domain.
|
||||
/// @note The (monotonic, period_count) values are not updated atomically. But such updates are
|
||||
/// bracketed by setting DANTE_BUFFERS_FLAG__TIMING_UPDATE_SYNC prior to starting an update and cleared
|
||||
/// when both fields have been updated. See the DanteTiming example code for a guide to how
|
||||
/// these two timing fields can be read to ensure correctness and consistency.
|
||||
uint64_t monotonic;
|
||||
} time;
|
||||
|
||||
} buffer_header_t;
|
||||
|
||||
//----------------------------------------------------------
|
||||
// Timing object subheader
|
||||
//----------------------------------------------------------
|
||||
|
||||
#define TIMING_OBJECT_NAME_LENGTH 256
|
||||
|
||||
enum
|
||||
{
|
||||
/// No timing synchronisation
|
||||
TIMING_OBJECT_TYPE__NONE = 0,
|
||||
|
||||
/// Posix semaphore or Windows Event.
|
||||
/// DEP signals the semaphore/event after each period. See buffer_header_t.time.samples_per_period for more details.
|
||||
TIMING_OBJECT_TYPE__SIGNAL_EVENT = 1
|
||||
};
|
||||
|
||||
/// Timing object subheader
|
||||
///
|
||||
/// Used to describe the timing synchronisation mechanism used between DEP and the application
|
||||
typedef struct timing_object_subheader
|
||||
{
|
||||
/// Length in bytes of this subheader
|
||||
uint32_t subheader_length_bytes;
|
||||
|
||||
/// The timing object type. Currently only TIMING_OBJECT_TYPE__SIGNAL_EVENT is used.
|
||||
uint32_t object_type;
|
||||
|
||||
/// Name of the timing object. The meaning of this name is dependent on the object type.
|
||||
/// If TIMING_OBJECT_TYPE__SIGNAL_EVENT is in use, this gives the name of the Posix semaphore
|
||||
/// or Windows signal event.
|
||||
char object_name[TIMING_OBJECT_NAME_LENGTH];
|
||||
} timing_object_subheader_t;
|
||||
|
||||
//----------------------------------------------------------
|
||||
// Platform-specific shared-memory implementation
|
||||
//----------------------------------------------------------
|
||||
|
||||
class SharedMemory
|
||||
{
|
||||
public:
|
||||
SharedMemory();
|
||||
~SharedMemory();
|
||||
|
||||
int connect(const std::string & name, bool globalNamespace);
|
||||
void disconnect();
|
||||
|
||||
void * get() const;
|
||||
|
||||
static std::string getErrorMessage(int err);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
Impl * mImpl;
|
||||
};
|
||||
|
||||
///
|
||||
// The main buffer handler class
|
||||
///
|
||||
class Buffers
|
||||
{
|
||||
public:
|
||||
Buffers();
|
||||
~Buffers();
|
||||
|
||||
/// Attempt to connect to the underlying shared memory buffer(s)
|
||||
///
|
||||
/// @param name Name of the shared memory buffer to connect to
|
||||
/// @param globalNamespace true if the name is in a global namespace and false otherwise.
|
||||
/// Only relevant for Windows platforms.
|
||||
/// @return 0 on success, otherwise a platform-specific error code
|
||||
int connect(const std::string & name, bool globalNamespace);
|
||||
|
||||
/// Disconnect from the underlying shared memory buffer(s)
|
||||
void disconnect();
|
||||
|
||||
/// Check whether currently connected to underlying shared memory buffer(s)
|
||||
/// @return true if connected and false otherwise
|
||||
bool isConnected() const { return mIsConnected; }
|
||||
|
||||
/// Check whether shared memory is within a global name space.
|
||||
///
|
||||
/// @note Only relevant for Windows platforms.
|
||||
/// @return true if a global name space is in use and false otherwise.
|
||||
bool isGlobalNamespace() const;
|
||||
|
||||
/// Get a pointer to the shared memory metadata block
|
||||
/// @return pointer to the start of the metadata block
|
||||
const volatile buffer_header_t * getHeader() const;
|
||||
|
||||
/// Get a pointer to the shared memory timing object subheader
|
||||
/// @return pointer to the start of the timing object subheader
|
||||
const timing_object_subheader_t * getTimingObjectSubheader() const;
|
||||
|
||||
/// Get audio buffer for the given Dante TX channel
|
||||
///
|
||||
/// Non-interleaved samples for the given TX channel can be written into the audio buffer
|
||||
/// @param index Zero based index of given Dante TX channel
|
||||
/// @return pointer to start of audio buffer for given index
|
||||
void * getDanteTxChannel(unsigned int index) const;
|
||||
|
||||
/// Get audio buffer for the given Dante RX channel
|
||||
///
|
||||
/// Non-interleaved samples for the given RX channel can be read from the audio buffer
|
||||
/// @param index Zero based index of given Dante RX channel
|
||||
/// @return pointer to start of audio buffer for given index
|
||||
void * getDanteRxChannel(unsigned int index) const;
|
||||
|
||||
private:
|
||||
bool mGlobalNamespace;
|
||||
SharedMemory mSharedMemory;
|
||||
SharedMemory mDanteTxSharedMemory;
|
||||
SharedMemory mDanteRxSharedMemory;
|
||||
|
||||
// cached data
|
||||
const volatile buffer_header_t * mHeader;
|
||||
const timing_object_subheader_t * mTimingObjectSubheader;
|
||||
std::vector<void *> mDanteTxChannels;
|
||||
std::vector<void *> mDanteRxChannels;
|
||||
|
||||
bool mIsConnected;
|
||||
};
|
||||
|
||||
|
||||
//----------------------------------------------------------
|
||||
// Platform-specific helper class that encapsulates run loop functionality
|
||||
//----------------------------------------------------------
|
||||
|
||||
class Timing
|
||||
{
|
||||
public:
|
||||
Timing();
|
||||
~Timing();
|
||||
|
||||
int open(const timing_object_subheader_t * subheader, bool globalNamespace);
|
||||
void close();
|
||||
|
||||
int wait();
|
||||
|
||||
static std::string getErrorMessage(int err);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
Impl * mImpl;
|
||||
};
|
||||
|
||||
typedef std::function<void (bool)> RunnerActiveChangedFn;
|
||||
|
||||
//----------------------------------------------------------
|
||||
// Helper class that encapsulates run loop functionality
|
||||
//----------------------------------------------------------
|
||||
|
||||
class Runner
|
||||
{
|
||||
public:
|
||||
Runner(Buffers & buffers);
|
||||
~Runner();
|
||||
|
||||
// run the main processing loop.
|
||||
// buffers must be connected.
|
||||
// Runs until 'running' is set to false or the buffer magic marker is cleared,
|
||||
// indicating that a disconnection is required.
|
||||
int run(bool & running, std::function<void(unsigned int n)> transferFn, std::function<void()> epochResetFn);
|
||||
|
||||
bool isActive() const;
|
||||
void setActiveChangedFn(RunnerActiveChangedFn fn) { mActiveChangedFn = fn; }
|
||||
|
||||
private:
|
||||
void setActive(bool active);
|
||||
|
||||
Buffers & mBuffers;
|
||||
const volatile buffer_header_t * mHeader;
|
||||
const timing_object_subheader_t * mTimingObjectSubheader;
|
||||
uint16_t mResetCount;
|
||||
uint64_t mPeriodCount;
|
||||
uint64_t mLastActiveMonotonicValue;
|
||||
bool mIsActive;
|
||||
RunnerActiveChangedFn mActiveChangedFn;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------
|
||||
// Memory Barrier helper function
|
||||
//----------------------------------------------------------
|
||||
|
||||
/*
|
||||
* The cpu and the compiler can reorder memory operations for the sake of optimisation.
|
||||
* However, this can break the synchronisation between DEP and the external application,
|
||||
* where we expect reads and writes to the shared memory to happen in a certain order.
|
||||
*
|
||||
* An acquire barrier ensures that any reads or writes sequenced after the barrier
|
||||
* are ordered after any reads before the barrier. For example, DEP writes to the RX
|
||||
* buffers and then updates the header.audio.period_count. Consider this pseudocode for
|
||||
* reading from the buffer:
|
||||
* ```
|
||||
* while(period_count == previous_period_count);
|
||||
* memory_barrier_acquire();
|
||||
* read_audio_data();
|
||||
* ```
|
||||
* The memory barrier here guarantees that the data read in read_audio_data() will be
|
||||
* at least as recent as the write to the period_count.
|
||||
*
|
||||
* This method is one way to implement an acquire memory barrier in a compiler and
|
||||
* architecture agnostic way.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4834) // ignore NODISCARD on dummyvar.load
|
||||
#endif
|
||||
static inline void memory_barrier_acquire() {
|
||||
volatile std::atomic<int> dummy_var;
|
||||
dummy_var.store(1);
|
||||
dummy_var.load(std::memory_order_relaxed);
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
//----------------------------------------------------------
|
||||
// Platform-specific helper functions
|
||||
//----------------------------------------------------------
|
||||
|
||||
uint64_t getMonotonicRate();
|
||||
|
||||
uint64_t getMonotonicValue();
|
||||
|
||||
void sleepMs(unsigned int ms);
|
||||
|
||||
} // namespace Dante
|
||||
//
|
||||
// Copyright © 2020-2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// Copyright © 2021 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.
|
||||
//
|
||||
// Priority.hpp
|
||||
// Priority modification handling
|
||||
//
|
||||
#pragma once
|
||||
|
||||
void setDantePriority(const char* progName);
|
||||
void cleanupDantePriority();
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// Copyright © 2021 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.
|
||||
//
|
||||
#pragma once
|
||||
#ifndef DEP_AUDIO_VERSIONS_H
|
||||
#define DEP_AUDIO_VERSIONS_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
extern const char * DEP_AUDIO_HASH_FULL ;
|
||||
extern const char * DEP_AUDIO_HASH_SHORT ;
|
||||
extern const bool DEP_AUDIO_STATE_CHANGES ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_HASH ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_HASH_SHORT ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_VERSION ;
|
||||
extern const bool DEP_AUDIO_COMMITS_SINCE_COMPONENT_TAG ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_VERSION_TYPE ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_VERSION_FULL ;
|
||||
extern const unsigned int DEP_AUDIO_VERSION_MAJOR ;
|
||||
extern const unsigned int DEP_AUDIO_VERSION_MINOR ;
|
||||
extern const unsigned int DEP_AUDIO_VERSION_PATCH ;
|
||||
extern const char * DEP_AUDIO_VERSION_SUFFIX ;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,61 @@
|
||||
CXX ?= g++
|
||||
ALSA_SRC_NAME ?= AlsaDevice
|
||||
|
||||
# path #
|
||||
SRC_PATH = src
|
||||
BUILD_PATH = build
|
||||
|
||||
# executable #
|
||||
LIB_NAME = libdep_audio.a
|
||||
|
||||
# extensions #
|
||||
SRC_EXT = cpp
|
||||
|
||||
SOURCES = $(shell find $(SRC_PATH) -name '*.$(SRC_EXT)' | sort -k 1nr | cut -f2-)
|
||||
OBJECTS = $(SOURCES:$(SRC_PATH)/%.$(SRC_EXT)=$(BUILD_PATH)/%.o)
|
||||
DEPS = $(OBJECTS:.o=.d)
|
||||
|
||||
# flags #
|
||||
COMPILE_FLAGS = -Wno-error=nonnull -fPIC -Wall -Werror -Wno-error=unknown-pragmas -Wno-error=unused-function -Wno-error=strict-aliasing -O2 -g -DNDEBUG -fPIC -std=gnu++11
|
||||
INCLUDES = -I include
|
||||
|
||||
ifneq (on, $(ALSA))
|
||||
SOURCES := $(filter-out src/$(ALSA_SRC_NAME).cpp,$(SOURCES))
|
||||
OBJECTS := $(filter-out build/$(ALSA_SRC_NAME).o,$(OBJECTS))
|
||||
DEP := $(filter-out build/$(ALSA_SRC_NAME).d,$(DEPS))
|
||||
else
|
||||
INCLUDES := $(INCLUDES) -I$(ALSA_INSTALL_PATH)/include
|
||||
endif
|
||||
|
||||
.PHONY: default_target
|
||||
default_target: release
|
||||
|
||||
.PHONY: release
|
||||
release: export CXXFLAGS := $(CXXFLAGS) $(COMPILE_FLAGS)
|
||||
release: dirs
|
||||
@$(MAKE) all
|
||||
|
||||
.PHONY: dirs
|
||||
dirs:
|
||||
@echo "Creating directories"
|
||||
@mkdir -p $(dir $(OBJECTS))
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@$(RM) -r $(BUILD_PATH)
|
||||
@$(RM) -r $(LIB_NAME)
|
||||
|
||||
# checks the executable and symlinks to the output
|
||||
.PHONY: all
|
||||
all: $(LIB_NAME)
|
||||
|
||||
# Creation of the executable
|
||||
$(LIB_NAME): $(OBJECTS)
|
||||
ar -r $(LIB_NAME) $(OBJECTS)
|
||||
|
||||
# Source file rules
|
||||
# After the first compilation they will be joined with the rules from the
|
||||
# dependency files to provide header dependencies
|
||||
$(BUILD_PATH)/%.o: $(SRC_PATH)/%.$(SRC_EXT)
|
||||
@echo "Compiling: $< -> $@"
|
||||
$(CXX) $(CXXFLAGS) $(INCLUDES) -MP -MMD -c $< -o $@
|
||||
@@ -0,0 +1,181 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// DanteBuffers.cpp
|
||||
// Audio buffers implementation
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
namespace Dante
|
||||
{
|
||||
|
||||
Buffers::Buffers() : mGlobalNamespace(), mSharedMemory(), mDanteTxSharedMemory(), mDanteRxSharedMemory(), mHeader(), mTimingObjectSubheader(), mDanteTxChannels(), mDanteRxChannels(), mIsConnected(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Buffers::~Buffers()
|
||||
{
|
||||
disconnect();
|
||||
}
|
||||
|
||||
// Attempt to connect to the underlying shared memory buffer(s)
|
||||
// returns 0 on success, otherwise a platform-specific error code
|
||||
int Buffers::connect(const std::string & name, bool globalNamespace)
|
||||
{
|
||||
if (mHeader)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return ERROR_INVALID_STATE;
|
||||
#else
|
||||
return EALREADY;
|
||||
#endif
|
||||
}
|
||||
mGlobalNamespace = globalNamespace;
|
||||
int result = mSharedMemory.connect(name, false);
|
||||
if (result)
|
||||
{
|
||||
disconnect();
|
||||
return result;
|
||||
}
|
||||
uint8_t * buf8 = (uint8_t *) mSharedMemory.get();
|
||||
mHeader = (const volatile buffer_header_t *) mSharedMemory.get();
|
||||
if (mHeader->metadata.timing_object_subheader_offset_bytes)
|
||||
{
|
||||
mTimingObjectSubheader = (timing_object_subheader_t *) (buf8 + mHeader->metadata.timing_object_subheader_offset_bytes);
|
||||
}
|
||||
mDanteTxChannels.resize(mHeader->audio.num_tx_channels);
|
||||
mDanteRxChannels.resize(mHeader->audio.num_rx_channels);
|
||||
uint8_t * tx0 = nullptr;
|
||||
uint8_t * rx0 = nullptr;
|
||||
if (mHeader->metadata.flags & DANTE_BUFFERS_FLAG__SEPARATE_CHANNEL_MEMORY)
|
||||
{
|
||||
result = mDanteTxSharedMemory.connect(name + "Tx", false);
|
||||
if (result)
|
||||
{
|
||||
disconnect();
|
||||
return result;
|
||||
}
|
||||
result = mDanteRxSharedMemory.connect(name + "Rx", false);
|
||||
if (result)
|
||||
{
|
||||
disconnect();
|
||||
return result;
|
||||
}
|
||||
tx0 = ((uint8_t *) mDanteTxSharedMemory.get()) + mHeader->metadata.first_tx_channel_offset_bytes;
|
||||
rx0 = ((uint8_t *) mDanteRxSharedMemory.get()) + mHeader->metadata.first_rx_channel_offset_bytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
tx0 = buf8 + mHeader->metadata.first_tx_channel_offset_bytes;
|
||||
rx0 = buf8 + mHeader->metadata.first_rx_channel_offset_bytes;
|
||||
}
|
||||
for (unsigned int c = 0; c < mHeader->audio.num_tx_channels; c++)
|
||||
{
|
||||
mDanteTxChannels[c] = (void *) (tx0 + c * mHeader->audio.bytes_per_channel);
|
||||
}
|
||||
for (unsigned int c = 0; c < mHeader->audio.num_rx_channels; c++)
|
||||
{
|
||||
mDanteRxChannels[c] = (void *) (rx0 + c * mHeader->audio.bytes_per_channel);
|
||||
}
|
||||
mIsConnected = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Disconnect from the underlying shared memory buffer(s)
|
||||
void Buffers::disconnect()
|
||||
{
|
||||
mSharedMemory.disconnect();
|
||||
mDanteTxSharedMemory.disconnect();
|
||||
mDanteRxSharedMemory.disconnect();
|
||||
|
||||
mHeader = nullptr;
|
||||
mTimingObjectSubheader = nullptr;
|
||||
mDanteTxChannels.clear();
|
||||
mDanteRxChannels.clear();
|
||||
mIsConnected = false;
|
||||
}
|
||||
|
||||
bool Buffers::isGlobalNamespace() const
|
||||
{
|
||||
return mGlobalNamespace;
|
||||
}
|
||||
|
||||
// Get a pointer to the shared memory metadata block
|
||||
const volatile buffer_header_t * Buffers::getHeader() const
|
||||
{
|
||||
return mHeader;
|
||||
}
|
||||
|
||||
const timing_object_subheader_t * Buffers::getTimingObjectSubheader() const
|
||||
{
|
||||
return mTimingObjectSubheader;
|
||||
}
|
||||
|
||||
// Get a pointer to the start of the audio buffer for the given TX channel
|
||||
void * Buffers::getDanteTxChannel(unsigned int index) const
|
||||
{
|
||||
return mDanteTxChannels[index];
|
||||
}
|
||||
|
||||
// Get a pointer to the start of the audio buffer for the given RX channel
|
||||
void * Buffers::getDanteRxChannel(unsigned int index) const
|
||||
{
|
||||
return mDanteRxChannels[index];
|
||||
}
|
||||
|
||||
};
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// Copyright © 2021-2023 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.
|
||||
//
|
||||
// DantePriority.cpp
|
||||
// Priority modification implementation
|
||||
//
|
||||
#ifdef WIN32
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "dante/Priority.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
void setDantePriority(const char* progName)
|
||||
{
|
||||
#if defined(WIN32)
|
||||
(void)(progName);
|
||||
SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
|
||||
timeBeginPeriod(1);
|
||||
#else
|
||||
// check if effective id is root user
|
||||
if (geteuid() == 0)
|
||||
{
|
||||
int policy;
|
||||
struct sched_param param;
|
||||
pthread_getschedparam(pthread_self(), &policy, ¶m);
|
||||
|
||||
// This thread should have a priority that is higher than the parent thread. Otherwise there is a chance it won't
|
||||
// be allowed to service the Dante buffers often enough, and this can cause a deadlock.
|
||||
// If the parent has a realtime scheduling policy, increase the priority if possible.
|
||||
// If the parent doesn't have realtime scheduling, give this thread the lowest realtime priority.
|
||||
if ((policy == SCHED_FIFO) || (policy == SCHED_RR))
|
||||
{
|
||||
if (param.sched_priority < sched_get_priority_max(policy))
|
||||
{
|
||||
param.sched_priority++;
|
||||
int ret = pthread_setschedparam(pthread_self(), policy, ¶m);
|
||||
if (ret != 0) std::cerr << "ERROR: increasing Dante thread priority failed. Error number: " << ret << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
policy = SCHED_FIFO;
|
||||
param.sched_priority = sched_get_priority_min(policy);
|
||||
|
||||
int ret = pthread_setschedparam(pthread_self(), policy, ¶m);
|
||||
if (ret != 0) std::cerr << "ERROR: setting Dante thread scheduling policy failed. Error number: " << ret << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
progName = progName != NULL ? progName : "The application";
|
||||
std::cerr << "WARNING: " << progName << " has not been run as root so thread priority will not be changed to realtime." << std::endl;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void cleanupDantePriority()
|
||||
{
|
||||
#if defined(WIN32)
|
||||
timeEndPeriod(1);
|
||||
#endif
|
||||
}
|
||||
//
|
||||
// Copyright © 2021-2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,211 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// DanteRunner.cpp
|
||||
// Audio runner implementation
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
|
||||
#ifdef WIN32
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace Dante
|
||||
{
|
||||
|
||||
#define RUNNER_INACTIVE_MAX_TIME_IN_SECS 1
|
||||
|
||||
Runner::Runner(Buffers & buffers) :
|
||||
mBuffers(buffers),
|
||||
mHeader(),
|
||||
mTimingObjectSubheader(),
|
||||
mResetCount(),
|
||||
mPeriodCount(),
|
||||
mLastActiveMonotonicValue(),
|
||||
mIsActive(),
|
||||
mActiveChangedFn()
|
||||
{
|
||||
}
|
||||
|
||||
Runner::~Runner()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool Runner::isActive() const
|
||||
{
|
||||
return mIsActive;
|
||||
}
|
||||
|
||||
void Runner::setActive(bool active)
|
||||
{
|
||||
if (mIsActive != active)
|
||||
{
|
||||
mIsActive = active;
|
||||
if (mActiveChangedFn)
|
||||
{
|
||||
mActiveChangedFn(active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Runner::run(bool & running, std::function<void(unsigned int n)> transferFn, std::function<void()> resetFn)
|
||||
{
|
||||
uint64_t monotonicRate = getMonotonicRate();
|
||||
uint64_t monotonicValue = getMonotonicValue();
|
||||
uint64_t lastMonotonicValue = 0;
|
||||
uint64_t monotonicSeconds = 0;
|
||||
uint64_t lastMonotonicSeconds = 0;
|
||||
uint64_t count = 0;
|
||||
uint64_t max = 0;
|
||||
|
||||
mHeader = mBuffers.getHeader();
|
||||
if (!mHeader)
|
||||
{
|
||||
return EINVAL;
|
||||
}
|
||||
mTimingObjectSubheader = mBuffers.getTimingObjectSubheader();
|
||||
|
||||
Timing timing;
|
||||
int result = timing.open(mTimingObjectSubheader, mBuffers.isGlobalNamespace());
|
||||
if (result)
|
||||
{
|
||||
std::cerr << "Error opening timing object: " << Timing::getErrorMessage(result) << std::endl;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool resetNeeded = true;
|
||||
|
||||
mResetCount = mHeader->metadata.reset_count;
|
||||
mPeriodCount = mHeader->time.period_count;
|
||||
|
||||
while (running && mHeader->metadata.magic_marker)
|
||||
{
|
||||
if (mResetCount != mHeader->metadata.reset_count || resetNeeded)
|
||||
{
|
||||
resetNeeded = false;
|
||||
|
||||
mResetCount = mHeader->metadata.reset_count;
|
||||
while (mResetCount & 0x1)
|
||||
{
|
||||
timing.wait();
|
||||
mResetCount = mHeader->metadata.reset_count;
|
||||
}
|
||||
// Ensure the read to period count is after the read from reset_count
|
||||
memory_barrier_acquire();
|
||||
|
||||
mPeriodCount = mHeader->time.period_count;
|
||||
if (resetFn) resetFn();
|
||||
|
||||
memory_barrier_acquire();
|
||||
// Ensure any reads from the header in resetFn come before checking reset_count again.
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mPeriodCount == mHeader->time.period_count)
|
||||
{
|
||||
timing.wait();
|
||||
}
|
||||
|
||||
monotonicValue = getMonotonicValue();
|
||||
|
||||
if (mPeriodCount < mHeader->time.period_count)
|
||||
{
|
||||
uint64_t n = mHeader->time.period_count - mPeriodCount;
|
||||
|
||||
// Ensure period count is read before reading audio data
|
||||
memory_barrier_acquire();
|
||||
|
||||
if (transferFn) transferFn(static_cast<unsigned int>(n));
|
||||
mPeriodCount += n;
|
||||
|
||||
mLastActiveMonotonicValue = monotonicValue;
|
||||
setActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((monotonicValue - mLastActiveMonotonicValue) > (getMonotonicRate() * RUNNER_INACTIVE_MAX_TIME_IN_SECS))
|
||||
{
|
||||
setActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
count++;
|
||||
uint64_t delta = monotonicValue - lastMonotonicValue;
|
||||
if (max < delta) max = delta;
|
||||
monotonicSeconds = monotonicValue / monotonicRate;
|
||||
|
||||
if (monotonicSeconds != lastMonotonicSeconds)
|
||||
{
|
||||
if (lastMonotonicSeconds)
|
||||
{
|
||||
//uint64_t maxUs = (max * 1000000) / monotonicRate;
|
||||
//std::cerr << lastMonotonicSeconds << ": count=" << count << " max=" << max << " (" << maxUs << "us)" << std::endl;
|
||||
}
|
||||
lastMonotonicSeconds = monotonicSeconds;
|
||||
count = 0;
|
||||
max = 0;
|
||||
}
|
||||
lastMonotonicValue = monotonicValue;
|
||||
}
|
||||
timing.close();
|
||||
mHeader = nullptr;
|
||||
mTimingObjectSubheader = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
};
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,210 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// DanteSharedMemory.cpp
|
||||
// Audio shared memory implementation
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
||||
namespace Dante
|
||||
{
|
||||
|
||||
static const size_t kNameLength = 31; // PSHMNAMLEN
|
||||
static const mode_t kMode = 0666; // S_IRUSR | S_IWUSR | S_IRGRP | S_IRGRP;
|
||||
|
||||
|
||||
class SharedMemory::Impl
|
||||
{
|
||||
public:
|
||||
Impl() : mName(), mFileDesc(), mSize(), mData() {}
|
||||
~Impl() { disconnect(); }
|
||||
|
||||
int connect(const std::string & name, bool globalNamespace)
|
||||
{
|
||||
if (name == "" || name.size() > kNameLength)
|
||||
{
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
if (mData)
|
||||
{
|
||||
return EALREADY;
|
||||
}
|
||||
|
||||
// Get ID for shared mem config block
|
||||
int fileDesc = shm_open(name.c_str(), O_RDWR, kMode);
|
||||
if (fileDesc < 0)
|
||||
{
|
||||
int err = errno;
|
||||
return err;
|
||||
}
|
||||
mFileDesc = fileDesc;
|
||||
mName = name;
|
||||
|
||||
struct stat s;
|
||||
if (fstat(mFileDesc, &s) < 0)
|
||||
{
|
||||
int err = errno;
|
||||
disconnect();
|
||||
return err;
|
||||
}
|
||||
if (s.st_size == 0)
|
||||
{
|
||||
// Creator has not yet resized the memory, try again later
|
||||
disconnect();
|
||||
return EAGAIN;
|
||||
}
|
||||
mSize = s.st_size;
|
||||
|
||||
// Map the memory
|
||||
void * raw = mmap(NULL, mSize, (PROT_READ | PROT_WRITE), MAP_SHARED, mFileDesc, 0);
|
||||
if (raw == MAP_FAILED)
|
||||
{
|
||||
// Creator has not yet resized, try again later
|
||||
int err = errno;
|
||||
disconnect();
|
||||
return err;
|
||||
}
|
||||
mData = raw;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void disconnect()
|
||||
{
|
||||
if (mData)
|
||||
{
|
||||
if (mSize)
|
||||
{
|
||||
munmap(mData, mSize);
|
||||
}
|
||||
mData = NULL;
|
||||
}
|
||||
|
||||
mSize = 0;
|
||||
|
||||
if (mFileDesc)
|
||||
{
|
||||
close(mFileDesc);
|
||||
mFileDesc = -1;
|
||||
}
|
||||
|
||||
if (mName.size())
|
||||
{
|
||||
mName = "";
|
||||
}
|
||||
}
|
||||
|
||||
void * get() const { return mData; }
|
||||
|
||||
private:
|
||||
std::string mName;
|
||||
int mFileDesc;
|
||||
size_t mSize;
|
||||
void * mData;
|
||||
};
|
||||
|
||||
SharedMemory::SharedMemory()
|
||||
{
|
||||
mImpl = new Impl();
|
||||
}
|
||||
|
||||
SharedMemory::~SharedMemory()
|
||||
{
|
||||
delete mImpl;
|
||||
}
|
||||
|
||||
int SharedMemory::connect(const std::string & name, bool globalNamespace)
|
||||
{
|
||||
return mImpl->connect(name, globalNamespace);
|
||||
}
|
||||
|
||||
void SharedMemory::disconnect()
|
||||
{
|
||||
mImpl->disconnect();
|
||||
}
|
||||
|
||||
void * SharedMemory::get() const
|
||||
{
|
||||
return mImpl->get();
|
||||
}
|
||||
|
||||
std::string SharedMemory::getErrorMessage(int err)
|
||||
{
|
||||
std::cerr << "ERROR is " << err << std::endl;
|
||||
if (!err) return std::string();
|
||||
|
||||
char tmp[1024];
|
||||
|
||||
// POSIX and GNU versions have different return types so we have to deal with both
|
||||
#if (__APPLE__ || ((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE))
|
||||
int result = strerror_r(err, tmp, sizeof(tmp));
|
||||
if (result == 0)
|
||||
return std::string();
|
||||
|
||||
return std::string(tmp);
|
||||
#else
|
||||
char * result = strerror_r(err, tmp, sizeof(tmp));
|
||||
return std::string(result);
|
||||
#endif
|
||||
}
|
||||
|
||||
}; // namespace Dante
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,203 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// DanteTiming.cpp
|
||||
// Audio timing implementation
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
|
||||
#include <errno.h>
|
||||
#include <semaphore.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
namespace Dante
|
||||
{
|
||||
|
||||
// Helper functions
|
||||
uint64_t getMonotonicRate()
|
||||
{
|
||||
return 1000000000ULL;
|
||||
}
|
||||
|
||||
uint64_t getMonotonicValue()
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
|
||||
uint64_t x;
|
||||
x = ts.tv_sec;
|
||||
x *= 1000000000ULL;
|
||||
x += ts.tv_nsec;
|
||||
return x;
|
||||
}
|
||||
|
||||
void sleepMs(unsigned int ms)
|
||||
{
|
||||
usleep(ms * 1000);
|
||||
}
|
||||
|
||||
class Timing::Impl
|
||||
{
|
||||
public:
|
||||
Impl() : mType(TIMING_OBJECT_TYPE__NONE), mSemaphore(SEM_FAILED) {}
|
||||
~Impl() { close(); }
|
||||
|
||||
|
||||
int open(const timing_object_subheader_t * subheader, bool globalNamespace)
|
||||
{
|
||||
mType = (subheader ? subheader->object_type : TIMING_OBJECT_TYPE__NONE);
|
||||
std::string name = (subheader ? subheader->object_name : "");
|
||||
|
||||
if (mType == TIMING_OBJECT_TYPE__SIGNAL_EVENT)
|
||||
{
|
||||
mSemaphore = sem_open(name.c_str(), 0);
|
||||
if (mSemaphore == SEM_FAILED)
|
||||
{
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
if (mSemaphore != SEM_FAILED)
|
||||
{
|
||||
sem_close(mSemaphore);
|
||||
mSemaphore = SEM_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
int wait()
|
||||
{
|
||||
switch (mType)
|
||||
{
|
||||
case TIMING_OBJECT_TYPE__SIGNAL_EVENT:
|
||||
{
|
||||
struct timespec ts;
|
||||
if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
|
||||
{
|
||||
return errno;
|
||||
}
|
||||
ts.tv_sec++;
|
||||
if (sem_timedwait(mSemaphore, &ts) == -1)
|
||||
{
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
sleepMs(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t mType;
|
||||
sem_t * mSemaphore;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
Timing::Timing()
|
||||
{
|
||||
mImpl = new Impl();
|
||||
}
|
||||
|
||||
Timing::~Timing()
|
||||
{
|
||||
if (mImpl)
|
||||
{
|
||||
delete mImpl;
|
||||
mImpl = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
int Timing::open(const timing_object_subheader_t * subheader, bool globalNamespace)
|
||||
{
|
||||
return mImpl->open(subheader, globalNamespace);
|
||||
}
|
||||
|
||||
void Timing::close()
|
||||
{
|
||||
mImpl->close();
|
||||
}
|
||||
|
||||
int Timing::wait()
|
||||
{
|
||||
return mImpl->wait();
|
||||
}
|
||||
|
||||
std::string Timing::getErrorMessage(int err)
|
||||
{
|
||||
if (!err) return std::string();
|
||||
|
||||
char tmp[1024];
|
||||
|
||||
// POSIX and GNU versions have different return types so we have to deal with both
|
||||
#if (__APPLE__ || ((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE))
|
||||
int result = strerror_r(err, tmp, sizeof(tmp));
|
||||
if (result == 0)
|
||||
return std::string();
|
||||
|
||||
return std::string(tmp);
|
||||
#else
|
||||
char * result = strerror_r(err, tmp, sizeof(tmp));
|
||||
return std::string(result);
|
||||
#endif
|
||||
}
|
||||
|
||||
};
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// Copyright © 2021 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.
|
||||
//
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "versions.h"
|
||||
|
||||
const char * DEP_AUDIO_HASH_FULL = "2cc23e7373f9c2bd0b126013f6361482d18e963e";
|
||||
const char * DEP_AUDIO_HASH_SHORT = "2cc23e73";
|
||||
const bool DEP_AUDIO_STATE_CHANGES = 0 ? true : false;
|
||||
const char * DEP_AUDIO_COMPONENT_TAG_HASH = "0e39aa90f4730d09407991125de47b946b9d788b";
|
||||
const char * DEP_AUDIO_COMPONENT_TAG_HASH_SHORT = "0e39aa90";
|
||||
const char * DEP_AUDIO_COMPONENT_TAG = "dep_audio_buffers_v1.0.0";
|
||||
const char * DEP_AUDIO_COMPONENT_TAG_VERSION = "1.0.0";
|
||||
const bool DEP_AUDIO_COMMITS_SINCE_COMPONENT_TAG = 1 ? true : false;
|
||||
const char * DEP_AUDIO_COMPONENT_TAG_VERSION_TYPE = "dev";
|
||||
const char * DEP_AUDIO_COMPONENT_TAG_VERSION_FULL = "1.0.0-dev";
|
||||
const unsigned int DEP_AUDIO_VERSION_MAJOR = 1;
|
||||
const unsigned int DEP_AUDIO_VERSION_MINOR = 0;
|
||||
const unsigned int DEP_AUDIO_VERSION_PATCH = 0;
|
||||
const char * DEP_AUDIO_VERSION_SUFFIX = "";
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,212 @@
|
||||
//
|
||||
// Copyright © 2021 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.
|
||||
//
|
||||
// dinfo.cpp
|
||||
// Buffer information dump example
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
#include "versions.h"
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#ifdef WIN32
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#define PRINTOUT_WIDTH 46
|
||||
|
||||
static bool g_running = true;
|
||||
|
||||
static void signalHandler(int sig)
|
||||
{
|
||||
(void) sig;
|
||||
g_running = false;
|
||||
signal(SIGINT, signalHandler);
|
||||
}
|
||||
|
||||
template<typename T1, typename T2> void printPair(T1 t1, T2 t2)
|
||||
{
|
||||
std::cout << " " << std::left << std::setw(30) << std::setfill(' ') << t1 << t2 << std::endl;
|
||||
}
|
||||
|
||||
class DanteInformation
|
||||
{
|
||||
public:
|
||||
DanteInformation(Dante::Buffers & buffers) : mBuffers(buffers) {}
|
||||
|
||||
void work(unsigned int numPeriods) {}
|
||||
|
||||
// Called when there are changes to the buffer header epoch or samplerate
|
||||
// values.
|
||||
void reset()
|
||||
{
|
||||
auto header = mBuffers.getHeader();
|
||||
|
||||
std::cout << std::string(PRINTOUT_WIDTH, '#') << std::endl;
|
||||
printPair("Reset count:", header->metadata.reset_count);
|
||||
printPair("", "");
|
||||
printPair("Audio Settings", "");
|
||||
printPair("Sample rate:", header->audio.sample_rate);
|
||||
printPair("Bits per sample:", header->audio.encoding);
|
||||
printPair("Samples per channel:", header->audio.samples_per_channel);
|
||||
printPair("Receiver channels:", header->audio.num_rx_channels);
|
||||
printPair("Transmitter channels:", header->audio.num_tx_channels);
|
||||
printPair("Bytes per channel:", header->audio.bytes_per_channel);
|
||||
printPair("", "");
|
||||
printPair("Timing Settings", "");
|
||||
printPair("Epoch seconds:", header->time.epoch_seconds);
|
||||
printPair("Epoch samples:", header->time.epoch_samples);
|
||||
printPair("Samples per period:", header->time.samples_per_period);
|
||||
printPair("Period count:", header->time.period_count);
|
||||
printPair("Clock drift ppb:", header->time.clock_drift_ppb);
|
||||
std::cout << std::string(PRINTOUT_WIDTH, '#') << std::endl;
|
||||
|
||||
g_running = false;
|
||||
}
|
||||
|
||||
private:
|
||||
Dante::Buffers & mBuffers;
|
||||
};
|
||||
|
||||
#define SHM_DEFAULT_NAME "DanteEP"
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
Dante::Buffers buffers;
|
||||
Dante::Runner runner(buffers);
|
||||
std::string shm = SHM_DEFAULT_NAME;
|
||||
|
||||
#ifdef WIN32
|
||||
if (argc > 1)
|
||||
{
|
||||
shm = argv[1];
|
||||
}
|
||||
#else
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "s:hv")) != -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 's':
|
||||
shm = optarg;
|
||||
break;
|
||||
case 'v':
|
||||
std::cout << getVersionInfo() << std::endl;
|
||||
return 0;
|
||||
case 'h':
|
||||
default:
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout <<
|
||||
"\t-h Show help" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-s Shared memory name to use - default is " << SHM_DEFAULT_NAME <<
|
||||
std::endl;
|
||||
std::cout << "\t-v Print out version" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
std::cout << "Using \"" << shm << "\" as shared memory" << std::endl;
|
||||
|
||||
#endif
|
||||
|
||||
DanteInformation danteInformation(buffers);
|
||||
|
||||
signal(SIGINT, signalHandler);
|
||||
|
||||
int lastConnectionResult = 0;
|
||||
while (g_running)
|
||||
{
|
||||
if (!lastConnectionResult)
|
||||
{
|
||||
std::cerr << "Connecting..." << std::endl;
|
||||
}
|
||||
int result = buffers.connect(shm, false);
|
||||
if (result)
|
||||
{
|
||||
if (result != lastConnectionResult)
|
||||
{
|
||||
lastConnectionResult = result;
|
||||
std::cerr << "Error connecting to shared memory: " << Dante::SharedMemory::getErrorMessage(result) << std::endl;
|
||||
std::cerr << "Trying to connect again..." << std::endl;
|
||||
}
|
||||
#ifdef WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
lastConnectionResult = 0;
|
||||
std::cerr << "Connected" << std::endl;
|
||||
|
||||
auto _work = [&danteInformation](unsigned int numPeriods) { danteInformation.work(numPeriods); };
|
||||
auto _reset = [&danteInformation]() { danteInformation.reset(); };
|
||||
|
||||
runner.run(g_running, _work, _reset);
|
||||
|
||||
std::cerr << "Disconnecting..." << std::endl;
|
||||
buffers.disconnect();
|
||||
std::cerr << "Disconnected" << std::endl;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
timeEndPeriod(1);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,299 @@
|
||||
//
|
||||
// Copyright © 2021 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.
|
||||
//
|
||||
// DanteLoopback.cpp
|
||||
// Audio loopback example
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
#include "dante/Priority.hpp"
|
||||
#include "versions.h"
|
||||
#include <assert.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#ifdef WIN32
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#if TRACEPOINTS
|
||||
#include <sys/sdt.h>
|
||||
#endif
|
||||
|
||||
static bool g_running = true;
|
||||
|
||||
static void signal_handler(int sig)
|
||||
{
|
||||
(void) sig;
|
||||
g_running = false;
|
||||
signal(SIGINT, signal_handler);
|
||||
}
|
||||
|
||||
class Loopback
|
||||
{
|
||||
public:
|
||||
Loopback(Dante::Buffers & buffers, int txLatencySamples)
|
||||
: mBuffers(buffers), mTxLatencySamples(txLatencySamples), mSamplesPerPeriod(), mSamplesPerChannel(),
|
||||
mDanteTxHeadSamples(), mDanteRxHeadSamples(), mDanteTxChannels(), mDanteRxChannels(), mNumLoopbackChannels()
|
||||
{}
|
||||
~Loopback() {}
|
||||
|
||||
// Unrolled RX and TX
|
||||
void workUnwrappedDanteRxDanteTx(unsigned int numSamples)
|
||||
{
|
||||
assert(mDanteTxHeadSamples + numSamples <= mSamplesPerChannel);
|
||||
assert(mDanteRxHeadSamples + numSamples <= mSamplesPerChannel);
|
||||
//std::cerr << "Transferring " << numSamples << " from " << mReadHead << " to " << mWriteHead << std::endl;
|
||||
unsigned int numBytes = numSamples * sizeof(int32_t);
|
||||
for (unsigned int c = 0; c < mNumLoopbackChannels; c++)
|
||||
{
|
||||
// We copy Dante RX -> Dante RX
|
||||
memcpy(mDanteTxChannels[c] + mDanteTxHeadSamples, mDanteRxChannels[c] + mDanteRxHeadSamples, numBytes);
|
||||
}
|
||||
mDanteTxHeadSamples += numSamples;
|
||||
mDanteRxHeadSamples += numSamples;
|
||||
}
|
||||
|
||||
void workUnwrappedDanteRx(unsigned int numSamples)
|
||||
{
|
||||
// Unwrap the Dante TX loop
|
||||
if (mDanteTxHeadSamples + numSamples > mSamplesPerChannel)
|
||||
{
|
||||
unsigned int n1 = mSamplesPerChannel - mDanteTxHeadSamples;
|
||||
unsigned int n2 = numSamples - n1;
|
||||
workUnwrappedDanteRxDanteTx(n1);
|
||||
assert(mDanteTxHeadSamples == mSamplesPerChannel);
|
||||
mDanteTxHeadSamples = 0;
|
||||
workUnwrappedDanteRxDanteTx(n2);
|
||||
}
|
||||
else
|
||||
{
|
||||
workUnwrappedDanteRxDanteTx(numSamples);
|
||||
}
|
||||
}
|
||||
|
||||
void work(unsigned int numPeriods)
|
||||
{
|
||||
unsigned int numSamples = numPeriods * mSamplesPerPeriod;
|
||||
|
||||
// For extreme stalls (e.g. breakpoints while debugging) just jump to catch up
|
||||
if (numSamples > mSamplesPerChannel)
|
||||
{
|
||||
numSamples = numSamples % mSamplesPerChannel;
|
||||
}
|
||||
|
||||
// Unwrap the Dante RX loop
|
||||
if (mDanteRxHeadSamples + numSamples > mSamplesPerChannel)
|
||||
{
|
||||
unsigned int n1 = mSamplesPerChannel - mDanteRxHeadSamples;
|
||||
unsigned int n2 = numSamples - n1;
|
||||
workUnwrappedDanteRx(n1);
|
||||
assert(mDanteRxHeadSamples == mSamplesPerChannel);
|
||||
mDanteRxHeadSamples = 0;
|
||||
workUnwrappedDanteRx(n2);
|
||||
}
|
||||
else
|
||||
{
|
||||
workUnwrappedDanteRx(numSamples);
|
||||
}
|
||||
|
||||
#if TRACEPOINTS
|
||||
DTRACE_PROBE1(loopback, samplepercycle, numSamples);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Called when there are changes to the buffer header epoch or samplerate
|
||||
// values.
|
||||
void reset()
|
||||
{
|
||||
auto metadata = mBuffers.getHeader();
|
||||
mSamplesPerPeriod = metadata->time.samples_per_period;
|
||||
mSamplesPerChannel = metadata->audio.samples_per_channel;
|
||||
|
||||
mDanteTxChannels.resize(mBuffers.getHeader()->audio.num_tx_channels);
|
||||
for (unsigned int i = 0; i < mDanteTxChannels.size(); i++)
|
||||
{
|
||||
mDanteTxChannels[i] = (int32_t *) mBuffers.getDanteTxChannel(i);
|
||||
}
|
||||
mDanteRxChannels.resize(mBuffers.getHeader()->audio.num_rx_channels);
|
||||
for (unsigned int i = 0; i < mDanteRxChannels.size(); i++)
|
||||
{
|
||||
mDanteRxChannels[i] = (const int32_t *) mBuffers.getDanteRxChannel(i);
|
||||
}
|
||||
if (mDanteTxChannels.size() < mDanteRxChannels.size())
|
||||
{
|
||||
mNumLoopbackChannels = (unsigned int) mDanteTxChannels.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
mNumLoopbackChannels = (unsigned int) mDanteRxChannels.size();
|
||||
}
|
||||
|
||||
mDanteRxHeadSamples = (unsigned int) ((metadata->time.period_count*mSamplesPerPeriod) % mSamplesPerChannel);
|
||||
mDanteTxHeadSamples = (unsigned int) ((metadata->time.period_count*mSamplesPerPeriod + mTxLatencySamples) % mSamplesPerChannel);
|
||||
}
|
||||
|
||||
private:
|
||||
Dante::Buffers & mBuffers;
|
||||
unsigned int mTxLatencySamples;
|
||||
|
||||
unsigned int mSamplesPerPeriod;
|
||||
unsigned int mSamplesPerChannel;
|
||||
unsigned int mDanteTxHeadSamples;
|
||||
unsigned int mDanteRxHeadSamples;
|
||||
std::vector<int32_t *> mDanteTxChannels;
|
||||
std::vector<const int32_t *> mDanteRxChannels;
|
||||
unsigned int mNumLoopbackChannels;
|
||||
};
|
||||
|
||||
#ifdef WIN32
|
||||
#define LOOPBACK_DEFAULT_TX_LATENCY_SAMPLES 480 // 10ms @ 48K
|
||||
#else
|
||||
#define LOOPBACK_DEFAULT_TX_LATENCY_SAMPLES 48 // 1ms @ 48K
|
||||
#endif
|
||||
|
||||
#define SHM_DEFAULT_NAME "DanteEP"
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
|
||||
Dante::Buffers buffers;
|
||||
Dante::Runner runner(buffers);
|
||||
std::string shm = SHM_DEFAULT_NAME;
|
||||
int txLatencySamples = LOOPBACK_DEFAULT_TX_LATENCY_SAMPLES;
|
||||
|
||||
#ifdef WIN32
|
||||
if (argc > 1)
|
||||
{
|
||||
shm = argv[1];
|
||||
}
|
||||
#else
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "l:s:v")) != -1) {
|
||||
switch (opt) {
|
||||
case 's':
|
||||
shm = optarg;
|
||||
break;
|
||||
case 'l':
|
||||
txLatencySamples = atoi(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
std::cout << getVersionInfo() << std::endl;
|
||||
return 0;
|
||||
default:
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout <<
|
||||
"\t-l Loopback Tx Latency (# samples) - default is " << LOOPBACK_DEFAULT_TX_LATENCY_SAMPLES <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-s Shared memory name to use - default is " << SHM_DEFAULT_NAME <<
|
||||
std::endl;
|
||||
std::cout << "\t-v Print out version" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::cout << "Using \"" << shm << "\" as shared memory" << std::endl;
|
||||
std::cout << "Using " << txLatencySamples << " samples as Tx Latency" << std::endl;
|
||||
|
||||
Loopback loopback(buffers, txLatencySamples);
|
||||
|
||||
setDantePriority(argv[0]);
|
||||
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
int lastConnectionResult = 0;
|
||||
while (g_running)
|
||||
{
|
||||
if (!lastConnectionResult)
|
||||
{
|
||||
std::cerr << "Connecting..." << std::endl;
|
||||
}
|
||||
int result = buffers.connect(shm, false);
|
||||
if (result)
|
||||
{
|
||||
if (result != lastConnectionResult)
|
||||
{
|
||||
lastConnectionResult = result;
|
||||
std::cerr << "Error connecting to shared memory: " << Dante::SharedMemory::getErrorMessage(result) << std::endl;
|
||||
std::cerr << "Trying to connect again..." << std::endl;
|
||||
}
|
||||
#ifdef WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
lastConnectionResult = 0;
|
||||
std::cerr << "Connected" << std::endl;
|
||||
|
||||
auto _work = [&loopback](unsigned int numPeriods) { loopback.work(numPeriods); };
|
||||
auto _reset = [&loopback]() { loopback.reset(); };
|
||||
runner.setActiveChangedFn([](bool active) {
|
||||
std::cerr << (active ? "Activated" : "Deactivated") << std::endl;
|
||||
});
|
||||
runner.run(g_running, _work, _reset);
|
||||
|
||||
std::cerr << "Disconnecting..." << std::endl;
|
||||
buffers.disconnect();
|
||||
std::cerr << "Disconnected" << std::endl;
|
||||
}
|
||||
|
||||
cleanupDantePriority();
|
||||
|
||||
return 0;
|
||||
}
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,610 @@
|
||||
//
|
||||
// Copyright © 2021 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.
|
||||
//
|
||||
// dplay.cpp
|
||||
// Audio play example
|
||||
//
|
||||
// prevent compiler warning
|
||||
#define _DEFAULT_SOURCE
|
||||
|
||||
#define DR_FLAC_IMPLEMENTATION
|
||||
#include "3rd_party/dr_flac.h"
|
||||
|
||||
#define DR_MP3_IMPLEMENTATION
|
||||
#include "3rd_party/dr_mp3.h"
|
||||
|
||||
#define DR_WAV_IMPLEMENTATION
|
||||
#include "3rd_party/dr_wav.h"
|
||||
|
||||
#include "dante/Buffers.hpp"
|
||||
#include "dante/Priority.hpp"
|
||||
#include "versions.h"
|
||||
#include <assert.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#ifdef WIN32
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#define START_CHANNEL_DEFAULT 1
|
||||
#define NUM_CHANNELS_DEFAULT 0
|
||||
|
||||
#define MIN(a, b) ( (a) < (b) ? (a) : (b) )
|
||||
|
||||
#define ZERO_INDEXED(n) ( (n) - 1)
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#if TRACEPOINTS
|
||||
#include <sys/sdt.h>
|
||||
#endif
|
||||
|
||||
#define MAX_SAMPLE_PROCESS_BLOCK 4096
|
||||
|
||||
static bool g_running = true;
|
||||
|
||||
static void signalHandler(int sig)
|
||||
{
|
||||
(void) sig;
|
||||
g_running = false;
|
||||
signal(SIGINT, signalHandler);
|
||||
}
|
||||
|
||||
class Decoder
|
||||
{
|
||||
public:
|
||||
virtual bool open(const char* filename) = 0;
|
||||
virtual uint32_t getSampleRate() = 0;
|
||||
virtual uint32_t getChannelCount() = 0;
|
||||
virtual unsigned int getSamples(unsigned int samplesPerChannel, int32_t sampleDecBuffers[]) = 0;
|
||||
virtual void seekStart() = 0;
|
||||
virtual void close() = 0;
|
||||
};
|
||||
|
||||
class FlacDecoder: public Decoder
|
||||
{
|
||||
public:
|
||||
bool open(const char* filename)
|
||||
{
|
||||
decoder = drflac_open_file(filename, NULL);
|
||||
if (decoder == NULL)
|
||||
{
|
||||
std::cerr << "ERROR: opening flac file " << std::endl;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Decoder opened successfully" << std::endl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
uint32_t getSampleRate()
|
||||
{
|
||||
return decoder->sampleRate;
|
||||
}
|
||||
uint32_t getChannelCount()
|
||||
{
|
||||
return decoder->channels;
|
||||
}
|
||||
unsigned int getSamples(unsigned int samplesPerChannel, int32_t sampleDecBuffers[])
|
||||
{
|
||||
return drflac_read_pcm_frames_s32(decoder, samplesPerChannel, sampleDecBuffers);
|
||||
}
|
||||
void seekStart()
|
||||
{
|
||||
drflac_seek_to_pcm_frame(decoder, 0);
|
||||
}
|
||||
void close()
|
||||
{
|
||||
drflac_close(decoder);
|
||||
std::cout << "Decoder closed successfully" << std::endl;
|
||||
}
|
||||
private:
|
||||
drflac* decoder;
|
||||
};
|
||||
|
||||
class MP3Decoder: public Decoder
|
||||
{
|
||||
public:
|
||||
bool open(const char* filename)
|
||||
{
|
||||
if (!drmp3_init_file(&decoder, filename, NULL))
|
||||
{
|
||||
std::cerr << "ERROR: opening mp3 file " << std::endl;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Decoder opened successfully" << std::endl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
uint32_t getSampleRate()
|
||||
{
|
||||
return decoder.sampleRate;
|
||||
}
|
||||
uint32_t getChannelCount()
|
||||
{
|
||||
return decoder.channels;
|
||||
}
|
||||
// the library presents 16-bit samples from the mp3 file into the buffer, we need to space these out to 32-bit samples
|
||||
unsigned int getSamples(unsigned int samplesPerChannel, int32_t sampleDecBuffers[])
|
||||
{
|
||||
if (samplesPerChannel == 0) return -1;
|
||||
unsigned int framesRead = drmp3_read_pcm_frames_s16(&decoder, samplesPerChannel, (drmp3_int16 *) sampleDecBuffers);
|
||||
// do an in-place copy of the 16-bit samples into the expected 32-bit format
|
||||
// the calling function takes into account the number of channels when asking for samples, so the buffer should be large enough
|
||||
for (int s = decoder.channels * framesRead - 1; s >= 0; s--)
|
||||
{
|
||||
sampleDecBuffers[s] = ((drmp3_int16 *)sampleDecBuffers)[s] << 16;
|
||||
}
|
||||
return framesRead;
|
||||
}
|
||||
void seekStart()
|
||||
{
|
||||
drmp3_seek_to_pcm_frame(&decoder, 0);
|
||||
}
|
||||
void close()
|
||||
{
|
||||
drmp3_uninit(&decoder);
|
||||
std::cout << "Decoder closed successfully" << std::endl;
|
||||
}
|
||||
private:
|
||||
drmp3 decoder;
|
||||
};
|
||||
|
||||
class WavDecoder: public Decoder
|
||||
{
|
||||
public:
|
||||
bool open(const char* filename)
|
||||
{
|
||||
if (!drwav_init_file(&decoder, filename, NULL))
|
||||
{
|
||||
std::cerr << "ERROR: opening wav file " << std::endl;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Decoder opened successfully" << std::endl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
uint32_t getSampleRate()
|
||||
{
|
||||
return decoder.sampleRate;
|
||||
}
|
||||
uint32_t getChannelCount()
|
||||
{
|
||||
return decoder.channels;
|
||||
}
|
||||
unsigned int getSamples(unsigned int samplesPerChannel, int32_t sampleDecBuffers[])
|
||||
{
|
||||
unsigned int framesRead = drwav_read_pcm_frames_s32(&decoder, samplesPerChannel, sampleDecBuffers);
|
||||
return framesRead;
|
||||
}
|
||||
void seekStart()
|
||||
{
|
||||
drwav_seek_to_pcm_frame(&decoder, 0);
|
||||
}
|
||||
void close()
|
||||
{
|
||||
drwav_uninit(&decoder);
|
||||
std::cout << "Decoder closed successfully" << std::endl;
|
||||
}
|
||||
private:
|
||||
drwav decoder;
|
||||
};
|
||||
|
||||
class Playback
|
||||
{
|
||||
public:
|
||||
Playback(Dante::Buffers & buffers, int txLatencySamples, Decoder * & decoder,
|
||||
unsigned int startChannel,int numChannels, bool repeat)
|
||||
: mBuffers(buffers), mTxLatencySamples(txLatencySamples), decoder(decoder), startChannel(startChannel),
|
||||
numChannels(numChannels), repeat(repeat), mSamplesPerPeriod(), mSamplesPerChannel(), mDanteTxHeadSamples(),
|
||||
mDanteRxHeadSamples(), mDanteTxChannels(), mDanteRxChannels(), mNumPlaybackChannels()
|
||||
{
|
||||
mDecoderChannelCount = decoder->getChannelCount();
|
||||
}
|
||||
~Playback()
|
||||
{
|
||||
decoder->close();
|
||||
}
|
||||
|
||||
void work(unsigned int numPeriods)
|
||||
{
|
||||
unsigned int numSamples = numPeriods * mSamplesPerPeriod;
|
||||
|
||||
// For extreme stalls (e.g. breakpoints while debugging) just jump to catch up
|
||||
if (numSamples > mSamplesPerChannel)
|
||||
{
|
||||
numSamples = numSamples % mSamplesPerChannel;
|
||||
}
|
||||
|
||||
// Unwrap the Dante TX loop
|
||||
if (mDanteTxHeadSamples + numSamples > mSamplesPerChannel)
|
||||
{
|
||||
unsigned int n1 = mSamplesPerChannel - mDanteTxHeadSamples;
|
||||
unsigned int n2 = numSamples - n1;
|
||||
pullSamplesFromDecoder(n1);
|
||||
assert(mDanteTxHeadSamples == mSamplesPerChannel);
|
||||
mDanteTxHeadSamples = 0;
|
||||
pullSamplesFromDecoder(n2);
|
||||
}
|
||||
else
|
||||
{
|
||||
pullSamplesFromDecoder(numSamples);
|
||||
}
|
||||
|
||||
#if TRACEPOINTS
|
||||
DTRACE_PROBE1(playback, samplepercycle, numSamples);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Called when there are changes to the buffer header epoch or samplerate
|
||||
// values.
|
||||
void reset()
|
||||
{
|
||||
auto metadata = mBuffers.getHeader();
|
||||
mSamplesPerPeriod = metadata->time.samples_per_period;
|
||||
mSamplesPerChannel = metadata->audio.samples_per_channel;
|
||||
|
||||
mDanteTxChannels.resize(mBuffers.getHeader()->audio.num_tx_channels);
|
||||
for (unsigned int i = 0; i < mDanteTxChannels.size(); i++)
|
||||
{
|
||||
mDanteTxChannels[i] = (int32_t *) mBuffers.getDanteTxChannel(i);
|
||||
}
|
||||
mDanteRxChannels.resize(mBuffers.getHeader()->audio.num_rx_channels);
|
||||
for (unsigned int i = 0; i < mDanteRxChannels.size(); i++)
|
||||
{
|
||||
mDanteRxChannels[i] = (const int32_t *) mBuffers.getDanteRxChannel(i);
|
||||
}
|
||||
if (mDanteTxChannels.size() < mDanteRxChannels.size())
|
||||
{
|
||||
mNumPlaybackChannels = (unsigned int) mDanteTxChannels.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
mNumPlaybackChannels = (unsigned int) mDanteRxChannels.size();
|
||||
}
|
||||
|
||||
mDanteRxHeadSamples = (unsigned int) ((metadata->time.period_count*mSamplesPerPeriod) % mSamplesPerChannel);
|
||||
mDanteTxHeadSamples = (unsigned int) ((metadata->time.period_count*mSamplesPerPeriod + mTxLatencySamples) % mSamplesPerChannel);
|
||||
|
||||
std::cout << "Config changed" << std::endl;
|
||||
if (numChannels == 0)
|
||||
{
|
||||
mActiveChannels = mDecoderChannelCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
mActiveChannels = MIN((unsigned int) numChannels, mDecoderChannelCount);
|
||||
}
|
||||
if (!sampleRatesMatch() || !enoughDEPChannels()) g_running = false;
|
||||
}
|
||||
|
||||
private:
|
||||
Dante::Buffers & mBuffers;
|
||||
|
||||
unsigned int mTxLatencySamples;
|
||||
Decoder * decoder;
|
||||
unsigned int startChannel;
|
||||
int numChannels;
|
||||
bool repeat;
|
||||
|
||||
unsigned int mSamplesPerPeriod;
|
||||
unsigned int mSamplesPerChannel;
|
||||
unsigned int mDanteTxHeadSamples;
|
||||
unsigned int mDanteRxHeadSamples;
|
||||
std::vector<int32_t *> mDanteTxChannels;
|
||||
std::vector<const int32_t *> mDanteRxChannels;
|
||||
unsigned int mNumPlaybackChannels;
|
||||
|
||||
int32_t sampleDecBuffers[MAX_SAMPLE_PROCESS_BLOCK];
|
||||
unsigned int mDecoderChannelCount;
|
||||
unsigned int mActiveChannels;
|
||||
|
||||
bool sampleRatesMatch()
|
||||
{
|
||||
uint32_t fileSampleRate = decoder->getSampleRate();
|
||||
uint32_t depSampleRate = mBuffers.getHeader()->audio.sample_rate;
|
||||
std::cerr << "file sample rate: " << fileSampleRate << " dep sample rate: " << depSampleRate << std::endl;
|
||||
if (fileSampleRate == depSampleRate)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Error: Sample rate mismatch, exiting..." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool enoughDEPChannels()
|
||||
{
|
||||
uint32_t fileChannelCount = decoder->getChannelCount();
|
||||
uint32_t depChannelCount = mBuffers.getHeader()->audio.num_tx_channels;
|
||||
std::cerr << "starting channel: " << startChannel + 1 << " | file channel count: " << fileChannelCount <<
|
||||
" | total dep channels: " << depChannelCount << " | user-defined channel count: " << numChannels;
|
||||
if (numChannels == 0) std::cerr << " -> " << fileChannelCount;
|
||||
std::cerr << std::endl;
|
||||
|
||||
if (startChannel + mActiveChannels <= depChannelCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Error: User asked for more channels than exist in dep, exiting..." << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void pullSamplesFromDecoder(unsigned int numSamples)
|
||||
{
|
||||
assert(mDanteTxHeadSamples + numSamples <= mSamplesPerChannel);
|
||||
|
||||
unsigned int samplesPerChannel;
|
||||
|
||||
unsigned int maxSamplesPerChannel = MAX_SAMPLE_PROCESS_BLOCK / mDecoderChannelCount;
|
||||
|
||||
do {
|
||||
samplesPerChannel = numSamples > maxSamplesPerChannel ? maxSamplesPerChannel : numSamples;
|
||||
|
||||
unsigned int samplesRead = decoder->getSamples(samplesPerChannel, sampleDecBuffers);
|
||||
if (samplesRead < samplesPerChannel)
|
||||
{
|
||||
if (repeat) decoder->seekStart();
|
||||
else g_running = false;
|
||||
}
|
||||
int32_t* sampleDecBuffers_ptr = sampleDecBuffers;
|
||||
// samples come out from the decoders in an interleaved fashion, ie
|
||||
// sample 1 channel 1, sample 1 channel 2, sample 2 channel 1, sample 2 channel 2... etc
|
||||
// we need to 'de-interleave' these samples and put each in the correct buffer
|
||||
for (unsigned int sample = 0; sample < samplesPerChannel; sample++)
|
||||
{
|
||||
for (unsigned int c = startChannel; c < startChannel + mActiveChannels; c++)
|
||||
{
|
||||
*(mDanteTxChannels[c] + mDanteTxHeadSamples + sample) = *sampleDecBuffers_ptr;
|
||||
sampleDecBuffers_ptr++;
|
||||
}
|
||||
if (mActiveChannels < mDecoderChannelCount)
|
||||
{
|
||||
sampleDecBuffers_ptr += (mDecoderChannelCount - mActiveChannels);
|
||||
}
|
||||
}
|
||||
mDanteTxHeadSamples += samplesRead;
|
||||
numSamples -= samplesRead;
|
||||
|
||||
} while (numSamples > 0 && g_running);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#ifdef WIN32
|
||||
#define PLAYBACK_DEFAULT_TX_LATENCY_SAMPLES 480 // 10ms @ 48K
|
||||
#else
|
||||
#define PLAYBACK_DEFAULT_TX_LATENCY_SAMPLES 48 // 1ms @ 48K
|
||||
#endif
|
||||
|
||||
#define SHM_DEFAULT_NAME "DanteEP"
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
|
||||
Dante::Buffers buffers;
|
||||
Dante::Runner runner(buffers);
|
||||
std::string shm = SHM_DEFAULT_NAME;
|
||||
int txLatencySamples = PLAYBACK_DEFAULT_TX_LATENCY_SAMPLES;
|
||||
unsigned int startChannel = START_CHANNEL_DEFAULT;
|
||||
int numChannels = NUM_CHANNELS_DEFAULT;
|
||||
bool repeat = false;
|
||||
|
||||
MP3Decoder mp3Decoder;
|
||||
WavDecoder wavDecoder;
|
||||
FlacDecoder flacDecoder;
|
||||
Decoder *decoder;
|
||||
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "l:s:r::c:n:hv")) != -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 's':
|
||||
shm = optarg;
|
||||
break;
|
||||
case 'l':
|
||||
txLatencySamples = atoi(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
startChannel = atoi(optarg);
|
||||
if (startChannel <= 0)
|
||||
{
|
||||
std::cout <<
|
||||
"Starting channel '-c' should be positive." <<
|
||||
std::endl;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
numChannels = atoi(optarg);
|
||||
if (numChannels < 0)
|
||||
{
|
||||
std::cout <<
|
||||
"Number of channels '-n' should be non-negative" <<
|
||||
std::endl;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
repeat = true;
|
||||
break;
|
||||
case 'v':
|
||||
std::cout << getVersionInfo() << std::endl;
|
||||
return 0;
|
||||
case 'h':
|
||||
default:
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout <<
|
||||
"\t-h Show help" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-l Playback Tx Latency (# samples) - default is " << PLAYBACK_DEFAULT_TX_LATENCY_SAMPLES <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-s Shared memory name to use - default is " << SHM_DEFAULT_NAME <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-c The first contiguous channel that the file will be played over - default is " << START_CHANNEL_DEFAULT <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-n The number of contiguous channels that will be used - default is " << NUM_CHANNELS_DEFAULT << std::endl <<
|
||||
"\t\t If this value is set to '0', the number of channels will be determined by the file" << std::endl <<
|
||||
"\t\t Specifying a number of outputs greater than the number of file channels will cause nothing to be send to the excess channels." <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-r Repeat forever" << std::endl <<
|
||||
std::endl;
|
||||
std::cout << "\t-v Print out version" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
std::cout << "Using \"" << shm << "\" as shared memory" << std::endl;
|
||||
std::cout << "Using " << txLatencySamples << " samples as Tx Latency" << std::endl;
|
||||
std::cout << "Starting channel: " << startChannel << std::endl;
|
||||
std::cout << "Number of channels: " << numChannels << std::endl;
|
||||
std::cout << "Repeat: " << (repeat ? "yes" : "no") << std::endl;
|
||||
|
||||
if (argv[optind] == NULL)
|
||||
{
|
||||
std::cerr << "Error: Input filename not provided." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* filename = argv[optind];
|
||||
const char* extension = strrchr(filename, '.');
|
||||
|
||||
if (strcmp(extension, ".mp3") == 0)
|
||||
{
|
||||
decoder = &mp3Decoder;
|
||||
}
|
||||
else if (strcmp(extension, ".wav") == 0)
|
||||
{
|
||||
decoder = &wavDecoder;
|
||||
}
|
||||
else if (strcmp(extension, ".flac") == 0)
|
||||
{
|
||||
decoder = &flacDecoder;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Format should be one of 'mp3', 'wav', 'flac'." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (!decoder->open(filename))
|
||||
{
|
||||
std::cerr << "There was an issue opening the file. Exiting..." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
Playback playback(buffers, txLatencySamples, decoder, ZERO_INDEXED(startChannel), numChannels, repeat);
|
||||
|
||||
setDantePriority(argv[0]);
|
||||
|
||||
signal(SIGINT, signalHandler);
|
||||
|
||||
int lastConnectionResult = 0;
|
||||
while (g_running)
|
||||
{
|
||||
if (!lastConnectionResult)
|
||||
{
|
||||
std::cerr << "Connecting..." << std::endl;
|
||||
}
|
||||
int result = buffers.connect(shm, false);
|
||||
if (result)
|
||||
{
|
||||
if (result != lastConnectionResult)
|
||||
{
|
||||
lastConnectionResult = result;
|
||||
std::cerr << "Error connecting to shared memory: " << Dante::SharedMemory::getErrorMessage(result) << std::endl;
|
||||
std::cerr << "Trying to connect again..." << std::endl;
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
lastConnectionResult = 0;
|
||||
std::cerr << "Connected" << std::endl;
|
||||
|
||||
auto _work = [&playback](unsigned int numPeriods) { playback.work(numPeriods); };
|
||||
auto _reset = [&playback]() { playback.reset(); };
|
||||
runner.setActiveChangedFn([](bool active) {
|
||||
std::cerr << (active ? "Activated" : "Deactivated") << std::endl;
|
||||
});
|
||||
runner.run(g_running, _work, _reset);
|
||||
|
||||
std::cerr << "Disconnecting..." << std::endl;
|
||||
buffers.disconnect();
|
||||
std::cerr << "Disconnected" << std::endl;
|
||||
}
|
||||
|
||||
cleanupDantePriority();
|
||||
|
||||
return 0;
|
||||
}
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,362 @@
|
||||
//
|
||||
// Copyright © 2021 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.
|
||||
//
|
||||
// drecord.cpp
|
||||
// Audio recording example
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
#include "dante/Priority.hpp"
|
||||
#include "versions.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#ifdef WIN32
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#if TRACEPOINTS
|
||||
#include <sys/sdt.h>
|
||||
#endif
|
||||
|
||||
/* WAV library */
|
||||
#include "3rd_party/wav.hpp"
|
||||
|
||||
static bool g_running = true;
|
||||
|
||||
static char *outFileName;
|
||||
|
||||
#define MAX_ENCODER_NUM 8
|
||||
#define MAX_DECODER_NUM 0
|
||||
#define OUT_MAX_FILE_NAME_LENGTH 256
|
||||
#define MAX_SAMPLE_PROCESS_BLOCK 4096
|
||||
|
||||
static void signal_handler(int sig)
|
||||
{
|
||||
(void) sig;
|
||||
g_running = false;
|
||||
signal(SIGINT, signal_handler);
|
||||
}
|
||||
|
||||
class Recorder
|
||||
{
|
||||
public:
|
||||
Recorder(Dante::Buffers & buffers, int txLatencySamples)
|
||||
: mBuffers(buffers), mTxLatencySamples(txLatencySamples), mSamplesPerPeriod(), mSamplesPerChannel(),
|
||||
mDanteTxHeadSamples(), mDanteRxHeadSamples(), mDanteTxChannels(), mDanteRxChannels(), mNumLoopbackChannels()
|
||||
{}
|
||||
~Recorder()
|
||||
{
|
||||
close_encoders();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
void work(unsigned int numPeriods)
|
||||
{
|
||||
unsigned int numSamples = numPeriods * mSamplesPerPeriod;
|
||||
|
||||
// For extreme stalls (e.g. breakpoints while debugging) just jump to catch up
|
||||
if (numSamples > mSamplesPerChannel)
|
||||
{
|
||||
numSamples = numSamples % mSamplesPerChannel;
|
||||
}
|
||||
|
||||
// Unwrap the Dante RX loop
|
||||
if (mDanteRxHeadSamples + numSamples > mSamplesPerChannel)
|
||||
{
|
||||
unsigned int n1 = mSamplesPerChannel - mDanteRxHeadSamples;
|
||||
unsigned int n2 = numSamples - n1;
|
||||
push_samples_to_encoder(n1);
|
||||
assert(mDanteRxHeadSamples == mSamplesPerChannel);
|
||||
mDanteRxHeadSamples = 0;
|
||||
push_samples_to_encoder(n2);
|
||||
}
|
||||
else
|
||||
{
|
||||
push_samples_to_encoder(numSamples);
|
||||
}
|
||||
|
||||
#if TRACEPOINTS
|
||||
DTRACE_PROBE1(loopback, samplepercycle, numSamples);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Called when there are changes to the buffer header epoch or samplerate
|
||||
// values.
|
||||
void reset()
|
||||
{
|
||||
auto metadata = mBuffers.getHeader();
|
||||
mSamplesPerPeriod = metadata->time.samples_per_period;
|
||||
mSamplesPerChannel = metadata->audio.samples_per_channel;
|
||||
|
||||
mDanteTxChannels.resize(mBuffers.getHeader()->audio.num_tx_channels);
|
||||
for (unsigned int i = 0; i < mDanteTxChannels.size(); i++)
|
||||
{
|
||||
mDanteTxChannels[i] = (int32_t *) mBuffers.getDanteTxChannel(i);
|
||||
}
|
||||
mDanteRxChannels.resize(mBuffers.getHeader()->audio.num_rx_channels);
|
||||
for (unsigned int i = 0; i < mDanteRxChannels.size(); i++)
|
||||
{
|
||||
mDanteRxChannels[i] = (const int32_t *) mBuffers.getDanteRxChannel(i);
|
||||
}
|
||||
if (mDanteTxChannels.size() < mDanteRxChannels.size())
|
||||
{
|
||||
mNumLoopbackChannels = (unsigned int) mDanteTxChannels.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
mNumLoopbackChannels = (unsigned int) mDanteRxChannels.size();
|
||||
}
|
||||
|
||||
mDanteRxHeadSamples = (unsigned int) ((metadata->time.period_count*mSamplesPerPeriod) % mSamplesPerChannel);
|
||||
mDanteTxHeadSamples = (unsigned int) ((metadata->time.period_count*mSamplesPerPeriod + mTxLatencySamples) % mSamplesPerChannel);
|
||||
|
||||
//first time reset is called dep is initialising
|
||||
if (mOutputFilePointer == NULL) {
|
||||
mWavInfo.num_channels = metadata->audio.num_rx_channels;
|
||||
mWavInfo.bits_per_sample = metadata->audio.encoding;
|
||||
mWavInfo.sample_rate = metadata->audio.sample_rate;
|
||||
mSampleBuffer = new int32_t[mWavInfo.num_channels];
|
||||
start_encoders();
|
||||
} else {
|
||||
std::cout << "Stopping recording because changes were made to dep config" << std::endl;
|
||||
g_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
void cleanup()
|
||||
{
|
||||
if (mSampleBuffer) delete mSampleBuffer;
|
||||
mSampleBuffer = NULL;
|
||||
}
|
||||
|
||||
private:
|
||||
Dante::Buffers & mBuffers;
|
||||
unsigned int mTxLatencySamples;
|
||||
|
||||
unsigned int mSamplesPerPeriod;
|
||||
unsigned int mSamplesPerChannel;
|
||||
unsigned int mDanteTxHeadSamples;
|
||||
unsigned int mDanteRxHeadSamples;
|
||||
std::vector<int32_t *> mDanteTxChannels;
|
||||
std::vector<const int32_t *> mDanteRxChannels;
|
||||
unsigned int mNumLoopbackChannels;
|
||||
|
||||
struct wav_info mWavInfo;
|
||||
FILE* mOutputFilePointer = NULL;
|
||||
uint32_t mTotalSamples = 0;
|
||||
int32_t* mSampleBuffer;
|
||||
|
||||
void start_encoders()
|
||||
{
|
||||
mOutputFilePointer = open_wav_file(outFileName);
|
||||
if(mOutputFilePointer == NULL)
|
||||
{
|
||||
std::cerr << "Error opening for output!" << std::endl;
|
||||
g_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
void close_encoders()
|
||||
{
|
||||
if (mOutputFilePointer != NULL)
|
||||
{
|
||||
mWavInfo.num_samples = mTotalSamples;
|
||||
write_wav_hdr(&mWavInfo, mOutputFilePointer);
|
||||
fclose(mOutputFilePointer);
|
||||
mOutputFilePointer = NULL;
|
||||
std::cout << "Encoder closed. Audio saved to '" << outFileName << "'." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void push_samples_to_encoder(unsigned int numSamples)
|
||||
{
|
||||
assert(mDanteRxHeadSamples + numSamples <= mSamplesPerChannel);
|
||||
|
||||
for (unsigned int i = 0; i < numSamples; i++)
|
||||
{
|
||||
for (unsigned int j = 0; j < mWavInfo.num_channels; j++)
|
||||
{
|
||||
mSampleBuffer[j] = *(mDanteRxChannels[j] + mDanteRxHeadSamples + i);
|
||||
}
|
||||
write_sample(&mWavInfo, mOutputFilePointer, mSampleBuffer);
|
||||
}
|
||||
|
||||
mTotalSamples += numSamples;
|
||||
mDanteRxHeadSamples += numSamples;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef WIN32
|
||||
#define LOOPBACK_DEFAULT_TX_LATENCY_SAMPLES 480 // 10ms @ 48K
|
||||
#else
|
||||
#define LOOPBACK_DEFAULT_TX_LATENCY_SAMPLES 480 // 10ms @ 48K
|
||||
#endif
|
||||
|
||||
#define SHM_DEFAULT_NAME "DanteEP"
|
||||
#define OUT_FILE_NAME_DEFAULT "sound.wav"
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
|
||||
Dante::Buffers buffers;
|
||||
Dante::Runner runner(buffers);
|
||||
std::string shm = SHM_DEFAULT_NAME;
|
||||
int txLatencySamples = LOOPBACK_DEFAULT_TX_LATENCY_SAMPLES;
|
||||
outFileName = (char*)OUT_FILE_NAME_DEFAULT;
|
||||
|
||||
#ifdef WIN32
|
||||
if (argc > 1)
|
||||
{
|
||||
shm = argv[1];
|
||||
}
|
||||
#else
|
||||
int opt;
|
||||
|
||||
while ((opt = getopt(argc, argv, "l:s:o:hv")) != -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 's':
|
||||
shm = optarg;
|
||||
break;
|
||||
case 'l':
|
||||
txLatencySamples = atoi(optarg);
|
||||
break;
|
||||
case 'o':
|
||||
outFileName = optarg;
|
||||
break;
|
||||
case 'v':
|
||||
std::cout << getVersionInfo() << std::endl;
|
||||
return 0;
|
||||
case 'h':
|
||||
default:
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout <<
|
||||
"\t-h Show help" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-l Loopback Tx Latency (# samples) - default is " << LOOPBACK_DEFAULT_TX_LATENCY_SAMPLES <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-s Shared memory name to use - default is " << SHM_DEFAULT_NAME <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-o output file name prefix - default is " << OUT_FILE_NAME_DEFAULT <<
|
||||
std::endl;
|
||||
std::cout << "\t-v Print out version" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::cout << "Using \"" << shm << "\" as shared memory" << std::endl;
|
||||
std::cout << "Using " << txLatencySamples << " samples as Tx Latency" << std::endl;
|
||||
|
||||
Recorder recorder(buffers, txLatencySamples);
|
||||
|
||||
setDantePriority(argv[0]);
|
||||
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
int lastConnectionResult = 0;
|
||||
while (g_running)
|
||||
{
|
||||
if (!lastConnectionResult)
|
||||
{
|
||||
std::cerr << "Connecting..." << std::endl;
|
||||
}
|
||||
int result = buffers.connect(shm, false);
|
||||
if (result)
|
||||
{
|
||||
if (result != lastConnectionResult)
|
||||
{
|
||||
lastConnectionResult = result;
|
||||
std::cerr << "Error connecting to shared memory: " << Dante::SharedMemory::getErrorMessage(result) << std::endl;
|
||||
std::cerr << "Trying to connect again..." << std::endl;
|
||||
}
|
||||
#ifdef WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
lastConnectionResult = 0;
|
||||
std::cerr << "Connected" << std::endl;
|
||||
|
||||
auto _work = [&recorder](unsigned int numPeriods) { recorder.work(numPeriods); };
|
||||
auto _reset = [&recorder]() { recorder.reset(); };
|
||||
runner.setActiveChangedFn([](bool active) {
|
||||
std::cerr << (active ? "Activated" : "Deactivated") << std::endl;
|
||||
});
|
||||
runner.run(g_running, _work, _reset);
|
||||
|
||||
recorder.cleanup();
|
||||
|
||||
std::cerr << "Disconnecting..." << std::endl;
|
||||
buffers.disconnect();
|
||||
std::cerr << "Disconnected" << std::endl;
|
||||
}
|
||||
|
||||
cleanupDantePriority();
|
||||
|
||||
return 0;
|
||||
}
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,11 @@
|
||||
pcm_type.dsoundcard {
|
||||
lib "/opt/dep/dsoundcard.so"
|
||||
}
|
||||
|
||||
pcm.dsoundcard {
|
||||
type dsoundcard
|
||||
hint {
|
||||
show on
|
||||
description "DEP Example Sound Card"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
//
|
||||
// Copyright © 2023 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.
|
||||
//
|
||||
// dsoundcard.hpp
|
||||
// Priority modification handling
|
||||
//
|
||||
#pragma once
|
||||
#include <semaphore.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <alsa/pcm_external.h>
|
||||
#include <sys/socket.h>
|
||||
#include <bits/stdc++.h>
|
||||
#include <mutex>
|
||||
|
||||
#include "dante/Buffers.hpp"
|
||||
#include "dsoundcard_logging.hpp"
|
||||
|
||||
#define SHM_DEFAULT_NAME "DanteEP"
|
||||
|
||||
#define PLAYBACK_DEFAULT_TX_LATENCY_MS 5
|
||||
|
||||
void startDEPSoundcard(snd_pcm_ioplug_t * io);
|
||||
void depSoundcardThread(snd_pcm_ioplug_t * io);
|
||||
void stopDEPSoundcard(snd_pcm_ioplug_t * io);
|
||||
|
||||
class IntermediateBuffer;
|
||||
class DEPSoundcard
|
||||
{
|
||||
public:
|
||||
DEPSoundcard(snd_pcm_ioplug_t * io, uint64_t & hwPtr, int pollSocket, sem_t* semaphore, uint32_t sampleRate);
|
||||
~DEPSoundcard();
|
||||
|
||||
void run();
|
||||
void stop();
|
||||
|
||||
bool pollDataAvailable();
|
||||
snd_pcm_sframes_t numFramesInIntermediateBuffer();
|
||||
|
||||
snd_pcm_sframes_t alsaToBuffer(void* buffer, snd_pcm_uframes_t frames, unsigned int channels);
|
||||
snd_pcm_sframes_t bufferToAlsa(void* buffer, snd_pcm_uframes_t frames, unsigned int channels);
|
||||
|
||||
private :
|
||||
void work(unsigned int numPeriods);
|
||||
void reset();
|
||||
|
||||
snd_pcm_uframes_t depRxToBuffer(unsigned int numFrames);
|
||||
snd_pcm_uframes_t bufferToDepTx(unsigned int numFrames);
|
||||
|
||||
void setThreadPriority();
|
||||
|
||||
private:
|
||||
Dante::Buffers mBuffers;
|
||||
snd_pcm_ioplug_t *mIo;
|
||||
ssize_t mBytesPerSample;
|
||||
uint64_t & mHwPtr;
|
||||
int mPollSocket;
|
||||
sem_t* mSemaphore;
|
||||
unsigned int mPeriodSizeInFrames;
|
||||
unsigned int mBufferSizeInFrames;
|
||||
unsigned int mDanteTxHeadSamples;
|
||||
unsigned int mDanteRxHeadSamples;
|
||||
std::vector<int32_t *> mDanteTxChannels;
|
||||
std::vector<const int32_t *> mDanteRxChannels;
|
||||
bool mRunning;
|
||||
IntermediateBuffer* intermediateBuffer;
|
||||
uint32_t mSampleRate;
|
||||
bool mDataAvailable;
|
||||
bool mXrunOccurred;
|
||||
};
|
||||
|
||||
class IntermediateBuffer
|
||||
{
|
||||
public:
|
||||
IntermediateBuffer(unsigned int channels, unsigned int bufferSizeInFrames, snd_pcm_stream_t mode, unsigned int samplerate);
|
||||
~IntermediateBuffer();
|
||||
|
||||
void reset(unsigned int channels, unsigned int bufferSizeInFrames);
|
||||
uint32_t getNumSamplesInBuffer();
|
||||
uint32_t getAvailableSpaceInSamplesInBuffer();
|
||||
|
||||
snd_pcm_uframes_t writeAlsaSamples(void *alsaBuffer, uint32_t numSamples, ssize_t bytesPerSample);
|
||||
snd_pcm_uframes_t readAlsaSamples(void *alsaBuffer, uint32_t numSamples, ssize_t bytesPerSample);
|
||||
|
||||
snd_pcm_uframes_t writeDepSamples(snd_pcm_format_t format,
|
||||
uint32_t numFrames,
|
||||
std::vector<const int32_t *> & danteRxChannels,
|
||||
uint32_t numAlsaChannels,
|
||||
uint32_t & danteRxHeadSamples,
|
||||
uint32_t bufferSizeInFrames);
|
||||
|
||||
snd_pcm_uframes_t readDepSamples(snd_pcm_format_t format,
|
||||
uint32_t numFrames,
|
||||
std::vector<int32_t *> & danteTxChannels,
|
||||
uint32_t numAlsaChannels,
|
||||
uint32_t & danteTxHeadSamples,
|
||||
uint32_t bufferSizeInFrames);
|
||||
|
||||
std::mutex mBufferMutex;
|
||||
|
||||
private:
|
||||
void resetBufferParams(unsigned int channels, unsigned int bufferSizeInFrames);
|
||||
|
||||
void writeAlsaSamplesToBuffer(uint8_t* buffer, uint32_t numSamples, ssize_t bytesPerSample);
|
||||
void readAlsaSamplesFromBuffer(uint8_t* buffer, uint32_t numSamples, ssize_t bytesPerSample);
|
||||
|
||||
uint32_t writeDepSamplesS16LE(uint32_t numFrames, std::vector<const int32_t *> & danteRxChannels, uint32_t numAlsaChannels, uint32_t & danteRxHeadSamples, uint32_t bufferSizeInFrames);
|
||||
uint32_t writeDepSamplesS32LE(uint32_t numFrames, std::vector<const int32_t *> & danteRxChannels, uint32_t numAlsaChannels, uint32_t & danteRxHeadSamples, uint32_t bufferSizeInFrames);
|
||||
uint32_t writeDepSamplesFLOATLE(uint32_t numFrames, std::vector<const int32_t *> & danteRxChannels, uint32_t numAlsaChannels, uint32_t & danteRxHeadSamples, uint32_t bufferSizeInFrames);
|
||||
|
||||
uint32_t readDepSamples16LE(uint32_t numFrames, std::vector<int32_t *> & danteTxChannels, uint32_t numAlsaChannels, uint32_t & danteTxHeadSamples, uint32_t bufferSizeInFrames);
|
||||
uint32_t readDepSamplesS32LE(uint32_t numFrames, std::vector<int32_t *> & danteTxChannels, uint32_t numAlsaChannels, uint32_t & danteTxHeadSamples, uint32_t bufferSizeInFrames);
|
||||
uint32_t readDepSamplesFLOATLE(uint32_t numFrames, std::vector<int32_t *> & danteTxChannels, uint32_t numAlsaChannels, uint32_t & danteTxHeadSamples, uint32_t bufferSizeInFrames);
|
||||
|
||||
int32_t* mBuffer;
|
||||
unsigned int mBufferSizeInSamples;
|
||||
unsigned int mWriteHead;
|
||||
unsigned int mReadHead;
|
||||
DEPSoundcardBufferLog *mBufferLog;
|
||||
uint32_t mBufferLevelInSamples; // N.B. having an explicit value for this lets us see that the buffer is full when the write pointer catches up with the read pointer
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
snd_pcm_ioplug_t io;
|
||||
int fd;
|
||||
uint64_t hw_ptr;
|
||||
unsigned int channels;
|
||||
sem_t semaphore;
|
||||
DEPSoundcard* dsoundcard;
|
||||
uint32_t dep_sample_rate;
|
||||
uint32_t dep_frames_per_period;
|
||||
} snd_pcm_dante_t;
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,171 @@
|
||||
//
|
||||
// Copyright © 2023 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.
|
||||
//
|
||||
// dsoundcard_logging.hpp
|
||||
// Generates timestamped stats log in csv format which can be imported into a spreadsheet to examine buffer levels and thread timings.
|
||||
//
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <sys/time.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
// Enable logging of intermediate buffer levels
|
||||
#define __ENABLE_BUFFER_LOGGING 0
|
||||
|
||||
// Enabling function trace logging logs calls to a CSV file, enabling just function trace outputs on stderr
|
||||
#define __ENABLE_FUNCTION_TRACE_LOGGING 0
|
||||
#define __ENABLE_FUNCTION_TRACE 0
|
||||
|
||||
// You can't log to both csv and stderr
|
||||
#if (__ENABLE_FUNCTION_TRACE_LOGGING == 1)
|
||||
#include <string.h>
|
||||
#undef __ENABLE_FUNCTION_TRACE
|
||||
#define __ENABLE_FUNCTION_TRACE 0
|
||||
#endif
|
||||
|
||||
class DEPSoundcardBufferLog
|
||||
{
|
||||
#if (__ENABLE_BUFFER_LOGGING == 1)
|
||||
|
||||
struct dsoundcardBufferLogEntry
|
||||
{
|
||||
uint64_t timestamp;
|
||||
uint16_t threadId;
|
||||
uint32_t samplesWritten;
|
||||
bool writeOperation;
|
||||
uint32_t newBufferLevel;
|
||||
};
|
||||
|
||||
public:
|
||||
DEPSoundcardBufferLog(const char *logFileName);
|
||||
~DEPSoundcardBufferLog();
|
||||
|
||||
void open();
|
||||
|
||||
void logWriteEvent(const uint32_t bufferLevelChange, const uint32_t bufferLevel);
|
||||
void logReadEvent(const uint32_t bufferLevelChange, const uint32_t bufferLevel);
|
||||
|
||||
std::ofstream mOutputFile;
|
||||
std::vector<struct dsoundcardBufferLogEntry> mLogEntries;
|
||||
|
||||
private:
|
||||
const uint32_t MaxNumLogEntries = 30000;
|
||||
|
||||
void displayWriteSampleRates(uint64_t buffTime);
|
||||
void displayReadSampleRates(uint64_t buffTime);
|
||||
void logEvent(const uint32_t bufferLevelChange, const uint32_t bufferLevel, bool write);
|
||||
|
||||
uint64_t mLogStartTime;
|
||||
pthread_t mFirstPthreadId;
|
||||
std::string mLogFileName;
|
||||
uint64_t mSamplesWritten;
|
||||
uint64_t mSamplesRead;
|
||||
uint64_t mPrevWriteSampleRateLogTime;
|
||||
uint64_t mPrevReadSampleRateLogTime;
|
||||
|
||||
#else
|
||||
public:
|
||||
DEPSoundcardBufferLog(const char *logFileName){};
|
||||
void logWriteEvent(const uint32_t bufferLevelChange, const uint32_t bufferLevel){};
|
||||
void logReadEvent(const uint32_t bufferLevelChange, const uint32_t bufferLevel){};
|
||||
#endif
|
||||
};
|
||||
|
||||
#if (__ENABLE_FUNCTION_TRACE_LOGGING == 1)
|
||||
class DEPSoundcardFunctionLog
|
||||
{
|
||||
static const uint32_t MaxFunctionNameLength = 32;
|
||||
struct dsoundcardFunctionLogEntry
|
||||
{
|
||||
uint64_t timestamp;
|
||||
uint64_t duration;
|
||||
char functionName[MaxFunctionNameLength + 1];
|
||||
};
|
||||
|
||||
public:
|
||||
DEPSoundcardFunctionLog(const char *logFileName);
|
||||
~DEPSoundcardFunctionLog();
|
||||
|
||||
void open();
|
||||
|
||||
uint64_t logFunctionStart(const char *functionName);
|
||||
void logFunctionEnd(const char *functionName, uint64_t startTime);
|
||||
|
||||
const uint32_t MaxNumLogEntries = 50000;
|
||||
|
||||
std::ofstream mOutputFile;
|
||||
std::vector<struct dsoundcardFunctionLogEntry> mLogEntries;
|
||||
|
||||
private:
|
||||
uint64_t mLogStartTime;
|
||||
std::string mLogFileName;
|
||||
};
|
||||
#endif
|
||||
|
||||
#if (__ENABLE_FUNCTION_TRACE_LOGGING == 1)
|
||||
extern DEPSoundcardFunctionLog functionLog;
|
||||
#define FUNCTION_TRACE_START() uint64_t startTime = functionLog.logFunctionStart(__FUNCTION__); (void)startTime
|
||||
#define FUNCTION_TRACE_END() functionLog.logFunctionEnd(__FUNCTION__, startTime)
|
||||
#else
|
||||
#if (__ENABLE_FUNCTION_TRACE == 1)
|
||||
#define FUNCTION_TRACE_START() fprintf(stderr, "%s @ %s:%d\n", __FUNCTION__, __FILE__, __LINE__); fflush(stderr)
|
||||
#define FUNCTION_TRACE_END() fprintf(stderr, "%s @ %s:%d END\n", __FUNCTION__, __FILE__, __LINE__); fflush(stderr)
|
||||
#else
|
||||
#define FUNCTION_TRACE_START() ;
|
||||
#define FUNCTION_TRACE_END() ;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
OUTPUT=$(install dsoundcard.so /opt/dep -C -v)
|
||||
|
||||
if [[ $OUTPUT ]]
|
||||
then
|
||||
echo $OUTPUT
|
||||
else
|
||||
echo "Shared library not modified"
|
||||
fi
|
||||
@@ -0,0 +1,420 @@
|
||||
//
|
||||
// Copyright © 2023 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.
|
||||
//
|
||||
// alsa_plugin.cpp
|
||||
// ALSA plugin example
|
||||
//
|
||||
#include "dsoundcard.hpp"
|
||||
|
||||
// ALSA buffer size expressed in periods.
|
||||
#define REQUESTED_PERIODS_IN_ALSA_BUFFER 16
|
||||
|
||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
|
||||
#define UNUSED_ARGS(args) (void)(args)
|
||||
|
||||
extern "C" {
|
||||
|
||||
static int depSetHwConstraint(snd_pcm_dante_t * dante, unsigned int maxChannels)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
unsigned int accessList[] =
|
||||
{
|
||||
//SND_PCM_ACCESS_MMAP_INTERLEAVED,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED
|
||||
//SND_PCM_ACCESS_MMAP_NONINTERLEAVED,
|
||||
//SND_PCM_ACCESS_RW_NONINTERLEAVED
|
||||
};
|
||||
|
||||
// Only support a few audio formats for now
|
||||
const unsigned int formats[] = {SND_PCM_FORMAT_S32, SND_PCM_FORMAT_S16, SND_PCM_FORMAT_FLOAT};
|
||||
|
||||
const unsigned int rates[] = { dante->dep_sample_rate };
|
||||
|
||||
snd_pcm_ioplug_t * io = &dante->io;
|
||||
|
||||
int result;
|
||||
|
||||
result = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS, ARRAY_SIZE(accessList), accessList);
|
||||
if (result < 0) return result;
|
||||
|
||||
result = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT, ARRAY_SIZE(formats), formats);
|
||||
if (result < 0) return result;
|
||||
|
||||
result = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS, 1, maxChannels);
|
||||
if (result < 0) return result;
|
||||
|
||||
result = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_RATE, ARRAY_SIZE(rates), rates);
|
||||
if (result < 0) return result;
|
||||
|
||||
// TODO - These values are not still correct, as we ideally want the ALSA period size for a
|
||||
// stream to be the same as the DEP period (in frames). With this method this is only true
|
||||
// for maxChannels (2) data of S32_LE samples. With, for example, 1 channel of S16_LE
|
||||
// the ALSA period is 4 times the DEP period - in other words the same number of bytes.
|
||||
// This does at least mean that the poll frequency expected is always a multiple of the DEP
|
||||
// frequency so we will live with it for now.
|
||||
// N.B. at this point we don't know the sample format or channels that will be used for the stream.
|
||||
unsigned int maxBytesPerPeriod = maxChannels * sizeof(uint32_t) * dante->dep_frames_per_period;
|
||||
unsigned int minBytesPerPeriod = 1 * sizeof(uint16_t) * dante->dep_frames_per_period;
|
||||
result = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, minBytesPerPeriod, maxBytesPerPeriod);
|
||||
if (result < 0) return result;
|
||||
|
||||
result = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS, REQUESTED_PERIODS_IN_ALSA_BUFFER, REQUESTED_PERIODS_IN_ALSA_BUFFER);
|
||||
if (result < 0) return result;
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// this callback is called before the audio app playing into the soundcard exits
|
||||
static int depSoundcardClose(snd_pcm_ioplug_t * io)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
stopDEPSoundcard(io);
|
||||
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t *)io->private_data;
|
||||
delete dante->dsoundcard;
|
||||
dante->dsoundcard = NULL;
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The return value of this function is used to set io->hw_ptr.
|
||||
// io->hw_ptr tells alsa how many samples have been consumed and it is used to determine whether
|
||||
// more samples should be read into the alsa buffer.
|
||||
static snd_pcm_sframes_t depSoundcardPointer(snd_pcm_ioplug_t * io)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t *)io->private_data;
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
return dante->hw_ptr;
|
||||
}
|
||||
|
||||
static int depSoundcardStart(snd_pcm_ioplug_t * io)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
startDEPSoundcard(io);
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// this callback is called before the audio app playing into the soundcard pauses or scrubs/seeks
|
||||
static int depSoundcardStop(snd_pcm_ioplug_t * io)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
stopDEPSoundcard(io);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// When alsa reads some more samples in from the audio app, this function gets called.
|
||||
// We simply read the samples into the intermediate buffers which we have already asserted
|
||||
// are large enough so as to not cause overruns.
|
||||
static snd_pcm_sframes_t depSoundcardPlaybackTransfer(snd_pcm_ioplug_t * io, const snd_pcm_channel_area_t * areas,
|
||||
snd_pcm_uframes_t offset, snd_pcm_uframes_t size)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t *)io->private_data;
|
||||
|
||||
char* buf = ((char*)areas->addr + (areas->first + areas->step * offset) / 8);
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
|
||||
return dante->dsoundcard->alsaToBuffer(buf, size, io->channels);
|
||||
}
|
||||
|
||||
// This function is called when an audio app recording from the soundcard requests some more samples.
|
||||
// We simply copy out whatever is currently in the intermediate buffers.
|
||||
// The timing of the request for samples is controlled by io->hw_ptr which in turn is controlled by the
|
||||
// work function in the dep soundcard, so everything keeps in sync.
|
||||
static snd_pcm_sframes_t depSoundcardCaptureTransfer(snd_pcm_ioplug_t * io, const snd_pcm_channel_area_t * areas,
|
||||
snd_pcm_uframes_t offset, snd_pcm_uframes_t size)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t*)(io->private_data);
|
||||
char* buf = ((char*)areas->addr + (areas->first + areas->step * offset) / 8);
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
|
||||
return dante->dsoundcard->bufferToAlsa(buf, size, io->channels);
|
||||
}
|
||||
|
||||
static int depHwParams(snd_pcm_ioplug_t * io, snd_pcm_hw_params_t * params)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t*)(io->private_data);
|
||||
|
||||
dante->channels = io->channels;
|
||||
unsigned int bytesPerSample = snd_pcm_format_physical_width(io->format) / 8;
|
||||
unsigned int bytesPerFrame = bytesPerSample * io->channels;
|
||||
|
||||
fprintf(stderr, "DSOUNDCARD HARDWARE PARAMETERS\n");
|
||||
fprintf(stderr, "Sample format: %s\n", snd_pcm_format_name(io->format));
|
||||
fprintf(stderr, "Channels: %u\n", dante->channels);
|
||||
fprintf(stderr, "Bytes per sample: %u\n", bytesPerSample);
|
||||
fprintf(stderr, "Bytes per frame: %u\n", bytesPerFrame);
|
||||
fprintf(stderr, "Stream direction: %s\n", (io->stream == SND_PCM_STREAM_PLAYBACK) ? "Playback" : "Capture");
|
||||
fprintf(stderr, "IO period size (frames) %lu\n", io->period_size);
|
||||
fprintf(stderr, "IO buffer size (frames) %lu\n", io->buffer_size);
|
||||
|
||||
if (!dante->dsoundcard)
|
||||
{
|
||||
// Now that we know the buffer sizes ALSA has set, we can create the intermediate buffer.
|
||||
dante->dsoundcard = new DEPSoundcard(io, dante->hw_ptr, dante->fd, &dante->semaphore, dante->dep_sample_rate);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int depPollRevents(snd_pcm_ioplug_t * io, struct pollfd * pfds, unsigned int nfds, unsigned short * revents)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
static char buf[1];
|
||||
|
||||
*revents = 0;
|
||||
|
||||
if (pfds == NULL || nfds != 1 || revents == NULL) return -EINVAL;
|
||||
|
||||
read(pfds[0].fd, buf, sizeof(buf));
|
||||
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t*)(io->private_data);
|
||||
if ((dante) && (dante->dsoundcard) && (dante->dsoundcard->pollDataAvailable()))
|
||||
{
|
||||
*revents = (io->stream == SND_PCM_STREAM_PLAYBACK) ? POLLOUT : POLLIN;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If you see the error message "DEP timing - semaphore wait: Connection timed out" appearing on the console,
|
||||
// try uncommenting this sleep statement. This sleep prevents alsaloop spinning round tightly on this poll
|
||||
// which can cause livelock on some low-powered systems
|
||||
// Be careful not to set this value higher than the period time.
|
||||
//usleep(500);
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns the delay in frames between a frame being captured in the hardware being pulled out of the
|
||||
// driver, or the delay between a frame being written to the device and being played out.
|
||||
// This function is optional, but is needed to work around a bug in some versions of alsalib -
|
||||
// see https://git.alsa-project.org/?p=alsa-lib.git;a=commitdiff;h=6cee452eabc5cfdf0a6955033b8ac8f6e12ea883
|
||||
static int depSoundcardDelay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
*delayp = 0;
|
||||
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t*)(io->private_data);
|
||||
if ((dante) && (dante->dsoundcard))
|
||||
{
|
||||
*delayp = dante->dsoundcard->numFramesInIntermediateBuffer();
|
||||
}
|
||||
FUNCTION_TRACE_END();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static snd_pcm_ioplug_callback_t depCapturePcmCallback =
|
||||
{
|
||||
.start = depSoundcardStart,
|
||||
.stop = depSoundcardStop,
|
||||
.pointer = depSoundcardPointer,
|
||||
.transfer = depSoundcardCaptureTransfer,
|
||||
.close = depSoundcardClose,
|
||||
.hw_params = depHwParams,
|
||||
.hw_free = NULL,
|
||||
.sw_params = NULL,
|
||||
.prepare = NULL,
|
||||
.drain = NULL,
|
||||
.pause = NULL,
|
||||
.resume = NULL,
|
||||
.poll_descriptors_count = NULL,
|
||||
.poll_descriptors = NULL,
|
||||
.poll_revents = depPollRevents,
|
||||
.dump = NULL,
|
||||
.delay = depSoundcardDelay,
|
||||
.query_chmaps = NULL,
|
||||
.get_chmap = NULL,
|
||||
.set_chmap = NULL
|
||||
};
|
||||
|
||||
static snd_pcm_ioplug_callback_t depPlaybackPcmCallback =
|
||||
{
|
||||
.start = depSoundcardStart,
|
||||
.stop = depSoundcardStop,
|
||||
.pointer = depSoundcardPointer,
|
||||
.transfer = depSoundcardPlaybackTransfer,
|
||||
.close = depSoundcardClose,
|
||||
.hw_params = depHwParams,
|
||||
.hw_free = NULL,
|
||||
.sw_params = NULL,
|
||||
.prepare = NULL,
|
||||
.drain = NULL,
|
||||
.pause = NULL,
|
||||
.resume = NULL,
|
||||
.poll_descriptors_count = NULL,
|
||||
.poll_descriptors = NULL,
|
||||
.poll_revents = depPollRevents,
|
||||
.dump = NULL,
|
||||
.delay = depSoundcardDelay,
|
||||
.query_chmaps = NULL,
|
||||
.get_chmap = NULL,
|
||||
.set_chmap = NULL
|
||||
};
|
||||
|
||||
// the poll fds have to be nonblocking if we are to use them for polling
|
||||
static int makeNonblock(int fd)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
int fl;
|
||||
|
||||
if ((fl = fcntl(fd, F_GETFL)) < 0) return fl;
|
||||
|
||||
if (fl & O_NONBLOCK) return 0;
|
||||
|
||||
return fcntl(fd, F_SETFL, fl | O_NONBLOCK);
|
||||
}
|
||||
|
||||
// first time setup tasks
|
||||
static int depSoundcardOpen(snd_pcm_t ** pcmp, const char * name, snd_pcm_stream_t stream, int mode)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
int err;
|
||||
int fd[2];
|
||||
snd_pcm_dante_t * dante;
|
||||
|
||||
dante = (snd_pcm_dante_t *)calloc(1, sizeof(*dante));
|
||||
if (!dante) return -ENOMEM;
|
||||
|
||||
dante->io.version = SND_PCM_IOPLUG_VERSION;
|
||||
dante->io.name = "ALSA <-> Dante PCM I/O Plugin";
|
||||
dante->io.callback = (stream == SND_PCM_STREAM_PLAYBACK) ? &depPlaybackPcmCallback : &depCapturePcmCallback;
|
||||
dante->io.private_data = dante;
|
||||
dante->io.mmap_rw = 0;
|
||||
|
||||
socketpair(AF_LOCAL, SOCK_STREAM, 0, fd);
|
||||
makeNonblock(fd[0]);
|
||||
makeNonblock(fd[1]);
|
||||
dante->fd = fd[0]; // File descriptor that DepSoundcard object writes to once per DEP period
|
||||
dante->io.poll_fd = fd[1]; // File descriptor that ALSA monitors / polls
|
||||
dante->io.poll_events = POLLIN;
|
||||
|
||||
sem_init(&dante->semaphore, 0, 0);
|
||||
|
||||
err = snd_pcm_ioplug_create(&dante->io, name, stream, mode);
|
||||
if (err < 0)
|
||||
{
|
||||
perror("depSoundcardOpen - call to snd_pcm_ioplug_create failed");
|
||||
free(dante);
|
||||
return err;
|
||||
}
|
||||
|
||||
// we connect to the dante buffers and get hw param information
|
||||
Dante::Buffers buffer;
|
||||
err = buffer.connect(SHM_DEFAULT_NAME, false);
|
||||
if (err) return -1;
|
||||
auto metadata = buffer.getHeader();
|
||||
unsigned int maxChannels = (stream == SND_PCM_STREAM_PLAYBACK ? metadata->audio.num_tx_channels : metadata->audio.num_rx_channels);
|
||||
dante->dep_sample_rate = metadata->audio.sample_rate;
|
||||
dante->dep_frames_per_period = metadata->time.samples_per_period;
|
||||
|
||||
buffer.disconnect();
|
||||
|
||||
err = depSetHwConstraint(dante, maxChannels);
|
||||
if (err < 0)
|
||||
{
|
||||
perror("depSoundcardOpen - call to depSetHwConstraint failed");
|
||||
snd_pcm_ioplug_delete(&dante->io);
|
||||
free(dante);
|
||||
return err;
|
||||
}
|
||||
|
||||
*pcmp = dante->io.pcm;
|
||||
snd_pcm_info_t * info = NULL;
|
||||
snd_pcm_info_alloca(&info);
|
||||
snd_pcm_info(*pcmp, info);
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
SND_PCM_PLUGIN_DEFINE_FUNC(dsoundcard)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
UNUSED_ARGS(root && conf);
|
||||
|
||||
int err = depSoundcardOpen(pcmp, name, stream, mode);
|
||||
return err;
|
||||
}
|
||||
|
||||
SND_PCM_PLUGIN_SYMBOL(dsoundcard);
|
||||
|
||||
} // extern "C"
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,331 @@
|
||||
//
|
||||
// Copyright © 2023 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.
|
||||
//
|
||||
// dsoundcard.cpp
|
||||
// DEP Soundcard example
|
||||
//
|
||||
#include "dsoundcard.hpp"
|
||||
#include "dante/Priority.hpp"
|
||||
|
||||
void startDEPSoundcard(snd_pcm_ioplug_t * io)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
// Occasionally "start" is called without "stop" being called first, e.g. when there is an xrun.
|
||||
// We need to reset the buffers when this happens.
|
||||
// There are checks in "stop" to make sure it does nothing if the worker thread is not running.
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t *)io->private_data;
|
||||
dante->dsoundcard->stop();
|
||||
|
||||
std::thread depSoundcard(depSoundcardThread, io);
|
||||
depSoundcard.detach();
|
||||
sem_wait(&dante->semaphore);
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
}
|
||||
|
||||
void depSoundcardThread(snd_pcm_ioplug_t * io)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t*)(io->private_data);
|
||||
dante->dsoundcard->run();
|
||||
}
|
||||
|
||||
void stopDEPSoundcard(snd_pcm_ioplug_t * io)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
snd_pcm_dante_t * dante = (snd_pcm_dante_t*)(io->private_data);
|
||||
if (dante->dsoundcard != NULL)
|
||||
{
|
||||
dante->dsoundcard->stop();
|
||||
}
|
||||
}
|
||||
|
||||
DEPSoundcard::DEPSoundcard(snd_pcm_ioplug_t * io, uint64_t & hwPtr, int pollSocket, sem_t* semaphore, uint32_t sampleRate)
|
||||
: mIo(io), mBytesPerSample(snd_pcm_format_width(io->format) / 8), mHwPtr(hwPtr),
|
||||
mPollSocket(pollSocket), mSemaphore(semaphore), mBufferSizeInFrames(), mDanteTxHeadSamples(),
|
||||
mDanteRxHeadSamples(), mDanteTxChannels(), mDanteRxChannels(), mRunning(false), intermediateBuffer(nullptr), mSampleRate(sampleRate),
|
||||
mDataAvailable(false), mXrunOccurred(false)
|
||||
{
|
||||
intermediateBuffer = new IntermediateBuffer(mIo->channels, mIo->buffer_size, mIo->stream, mSampleRate);
|
||||
}
|
||||
|
||||
DEPSoundcard::~DEPSoundcard()
|
||||
{
|
||||
delete intermediateBuffer;
|
||||
}
|
||||
|
||||
void DEPSoundcard::run()
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
setThreadPriority();
|
||||
|
||||
mHwPtr = 0;
|
||||
mRunning = true;
|
||||
|
||||
Dante::Runner runner(mBuffers);
|
||||
|
||||
int lastConnectionResult = 0;
|
||||
while (mRunning)
|
||||
{
|
||||
if (!lastConnectionResult)
|
||||
{
|
||||
std::cerr << "Connecting..." << std::endl;
|
||||
}
|
||||
int result = mBuffers.connect(SHM_DEFAULT_NAME, false);
|
||||
if (result)
|
||||
{
|
||||
if (result != lastConnectionResult)
|
||||
{
|
||||
lastConnectionResult = result;
|
||||
std::cerr << "Error connecting to shared memory: " << Dante::SharedMemory::getErrorMessage(result) << std::endl;
|
||||
std::cerr << "Trying to connect again..." << std::endl;
|
||||
}
|
||||
sleep(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
lastConnectionResult = 0;
|
||||
std::cerr << "Connected" << std::endl;
|
||||
|
||||
auto _work = [this](unsigned int numPeriods) { work(numPeriods); };
|
||||
auto _reset = [this]() { reset(); };
|
||||
runner.setActiveChangedFn([this](bool active) {
|
||||
std::cerr << (active ? "Activated" : "Deactivated") << std::endl;
|
||||
if (active)
|
||||
{
|
||||
sem_post(mSemaphore); // Unblock startDEPSoundcard
|
||||
}
|
||||
});
|
||||
runner.run(mRunning, _work, _reset);
|
||||
|
||||
std::cerr << "Disconnecting..." << std::endl;
|
||||
mBuffers.disconnect();
|
||||
std::cerr << "Disconnected" << std::endl;
|
||||
|
||||
usleep(10000); // Make sure we allow things time to recover
|
||||
} // mRunning is false, therefore we are exiting
|
||||
cleanupDantePriority();
|
||||
// notify exit completion
|
||||
sem_post(mSemaphore); // Unblock DEPSoundcard::stop
|
||||
}
|
||||
|
||||
void DEPSoundcard::stop()
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
if (mRunning)
|
||||
{
|
||||
mRunning = false;
|
||||
|
||||
// Wait for Runner thread to exit.
|
||||
sem_wait(mSemaphore);
|
||||
|
||||
delete intermediateBuffer;
|
||||
intermediateBuffer = new IntermediateBuffer(mIo->channels, mIo->buffer_size, mIo->stream, mSampleRate);
|
||||
}
|
||||
|
||||
FUNCTION_TRACE_END();
|
||||
}
|
||||
|
||||
void DEPSoundcard::work(unsigned int numPeriods)
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
unsigned int numFrames = numPeriods * mPeriodSizeInFrames;
|
||||
|
||||
const std::lock_guard<std::mutex> lock(intermediateBuffer->mBufferMutex);
|
||||
|
||||
// depending on whether the soundcard is in capture or playback mode, we will be writing to or reading from some intermediate buffers
|
||||
if (mIo->stream == SND_PCM_STREAM_CAPTURE)
|
||||
{
|
||||
if (depRxToBuffer(numFrames) < numFrames)
|
||||
{
|
||||
mXrunOccurred = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bufferToDepTx(numFrames) < numFrames)
|
||||
{
|
||||
mXrunOccurred = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Make the poll function return true now.
|
||||
mDataAvailable = true;
|
||||
|
||||
// Hardware pointer represents the position of the Dante shared memory read/write pointers
|
||||
mHwPtr += numFrames;
|
||||
mHwPtr %= mIo->buffer_size;
|
||||
|
||||
// Write some data into the poll socket. The data itself is not important, but the act of
|
||||
// writing will wake up the polling client.
|
||||
char dummyByte;
|
||||
if (write(mPollSocket, &dummyByte, 1) < 0)
|
||||
{
|
||||
perror("DEPsoundcard Failed to write poll file descriptor");
|
||||
}
|
||||
FUNCTION_TRACE_END();
|
||||
}
|
||||
|
||||
// Called when there are changes to the buffer header epoch or samplerate
|
||||
// values and on startup
|
||||
void DEPSoundcard::reset()
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
auto metadata = mBuffers.getHeader();
|
||||
|
||||
uint32_t prevSampleRate = mSampleRate;
|
||||
|
||||
unsigned int newNumChannels =
|
||||
(mIo->stream == SND_PCM_STREAM_CAPTURE) ?
|
||||
mBuffers.getHeader()->audio.num_rx_channels :
|
||||
mBuffers.getHeader()->audio.num_tx_channels;
|
||||
|
||||
mPeriodSizeInFrames = metadata->time.samples_per_period;
|
||||
mBufferSizeInFrames = metadata->audio.samples_per_channel;
|
||||
mSampleRate = metadata->audio.sample_rate;
|
||||
|
||||
mDanteTxChannels.resize(mBuffers.getHeader()->audio.num_tx_channels);
|
||||
for (unsigned int i = 0; i < mDanteTxChannels.size(); i++)
|
||||
{
|
||||
mDanteTxChannels[i] = (int32_t *) mBuffers.getDanteTxChannel(i);
|
||||
}
|
||||
mDanteRxChannels.resize(mBuffers.getHeader()->audio.num_rx_channels);
|
||||
for (unsigned int i = 0; i < mDanteRxChannels.size(); i++)
|
||||
{
|
||||
mDanteRxChannels[i] = (const int32_t *) mBuffers.getDanteRxChannel(i);
|
||||
}
|
||||
|
||||
mDanteRxHeadSamples = (unsigned int) ((metadata->time.period_count*mPeriodSizeInFrames) % mBufferSizeInFrames);
|
||||
unsigned int playbackLatency = PLAYBACK_DEFAULT_TX_LATENCY_MS * mSampleRate / 1000;
|
||||
mDanteTxHeadSamples = (unsigned int) ((metadata->time.period_count*mPeriodSizeInFrames + playbackLatency) % mBufferSizeInFrames);
|
||||
|
||||
if ((mSampleRate) && (mSampleRate != prevSampleRate))
|
||||
{
|
||||
std::cerr << "Detected change to sample rate from " << prevSampleRate << " to " << mSampleRate << " - exiting." << std::endl;
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (newNumChannels < mIo->channels)
|
||||
{
|
||||
std::cerr << "Detected change to number of channels from " << mIo->channels << " to " << newNumChannels << " - exiting." << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
FUNCTION_TRACE_END();
|
||||
}
|
||||
|
||||
bool DEPSoundcard::pollDataAvailable()
|
||||
{
|
||||
bool retval = mDataAvailable;
|
||||
mDataAvailable = false;
|
||||
return retval;
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t DEPSoundcard::numFramesInIntermediateBuffer()
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(intermediateBuffer->mBufferMutex);
|
||||
|
||||
return intermediateBuffer->getNumSamplesInBuffer() / mIo->channels;
|
||||
}
|
||||
|
||||
snd_pcm_uframes_t DEPSoundcard::depRxToBuffer(unsigned int numFrames)
|
||||
{
|
||||
return intermediateBuffer->writeDepSamples(mIo->format, numFrames, mDanteRxChannels, mIo->channels, mDanteRxHeadSamples, mBufferSizeInFrames);
|
||||
}
|
||||
|
||||
snd_pcm_uframes_t DEPSoundcard::bufferToDepTx(unsigned int numFrames)
|
||||
{
|
||||
return intermediateBuffer->readDepSamples(mIo->format, numFrames, mDanteTxChannels, mIo->channels, mDanteTxHeadSamples, mBufferSizeInFrames);
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t DEPSoundcard::alsaToBuffer(void* buffer, snd_pcm_uframes_t frames, unsigned int channels)
|
||||
{
|
||||
// Return number of frames written
|
||||
assert(channels == mIo->channels);
|
||||
|
||||
if (mXrunOccurred)
|
||||
{
|
||||
mXrunOccurred = false;
|
||||
return -EPIPE;
|
||||
}
|
||||
|
||||
return (intermediateBuffer->writeAlsaSamples(buffer, frames * channels, mBytesPerSample) / channels);
|
||||
}
|
||||
|
||||
snd_pcm_sframes_t DEPSoundcard::bufferToAlsa(void* buffer, snd_pcm_uframes_t frames, unsigned int channels)
|
||||
{
|
||||
assert(channels == mIo->channels);
|
||||
|
||||
if (mXrunOccurred)
|
||||
{
|
||||
mXrunOccurred = false;
|
||||
return -EPIPE;
|
||||
}
|
||||
|
||||
// Return number of frames read
|
||||
return (intermediateBuffer->readAlsaSamples(buffer, frames * channels, mBytesPerSample) / channels);
|
||||
}
|
||||
|
||||
void DEPSoundcard::setThreadPriority()
|
||||
{
|
||||
FUNCTION_TRACE_START();
|
||||
|
||||
setDantePriority("The soundcard");
|
||||
}
|
||||
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,438 @@
|
||||
//
|
||||
// Copyright © 2023 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.
|
||||
//
|
||||
// dsoundcard_intermediatebuffer.cpp
|
||||
// DEP Soundcard example
|
||||
//
|
||||
#include "dsoundcard.hpp"
|
||||
|
||||
IntermediateBuffer::IntermediateBuffer(unsigned int channels, unsigned int bufferSizeInFrames, snd_pcm_stream_t mode, unsigned int samplerate)
|
||||
: mBufferLevelInSamples(0)
|
||||
{
|
||||
mBuffer = (int32_t*) calloc(channels * bufferSizeInFrames, sizeof(int32_t));
|
||||
|
||||
resetBufferParams(channels, bufferSizeInFrames);
|
||||
|
||||
if (mode == SND_PCM_STREAM_CAPTURE)
|
||||
{
|
||||
mBufferLog = new DEPSoundcardBufferLog("/tmp/dep_rx_soundcard_log.csv");
|
||||
}
|
||||
else
|
||||
{
|
||||
mBufferLog = new DEPSoundcardBufferLog("/tmp/dep_tx_soundcard_log.csv");
|
||||
}
|
||||
}
|
||||
|
||||
IntermediateBuffer::~IntermediateBuffer()
|
||||
{
|
||||
delete(mBufferLog);
|
||||
free(mBuffer);
|
||||
}
|
||||
|
||||
void IntermediateBuffer::reset(unsigned int channels, unsigned int bufferSizeInFrames)
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mBufferMutex);
|
||||
unsigned int newBufferSizeInSamples = channels * bufferSizeInFrames;
|
||||
|
||||
if ((mBuffer == nullptr) || (newBufferSizeInSamples != mBufferSizeInSamples))
|
||||
{
|
||||
int32_t *oldBuffer = mBuffer;
|
||||
mBuffer = (int32_t*) calloc(channels * bufferSizeInFrames, sizeof(int32_t));
|
||||
|
||||
if (oldBuffer)
|
||||
{
|
||||
free(oldBuffer);
|
||||
}
|
||||
}
|
||||
resetBufferParams(channels, bufferSizeInFrames);
|
||||
}
|
||||
|
||||
void IntermediateBuffer::resetBufferParams(unsigned int channels, unsigned int bufferSizeInFrames)
|
||||
{
|
||||
mBufferSizeInSamples = channels * bufferSizeInFrames;
|
||||
mWriteHead = 0;
|
||||
mReadHead = 0;
|
||||
mBufferLevelInSamples = 0;
|
||||
}
|
||||
|
||||
uint32_t IntermediateBuffer::getNumSamplesInBuffer()
|
||||
{
|
||||
#if 0
|
||||
// Check that we are recording the buffer level correctly.
|
||||
// The second part of this clause excludes the case where the buffer is full -
|
||||
// here the calculated buffer level will be 0 (because read ptr == write ptr) but the actual buffer level will be mBufferSizeInSamples
|
||||
uint32_t calcBufferLevel = (mBufferSizeInSamples + mWriteHead - mReadHead) % mBufferSizeInSamples;
|
||||
if ((calcBufferLevel != mBufferLevelInSamples) && (!((calcBufferLevel == 0)&&(mBufferLevelInSamples == mBufferSizeInSamples))))
|
||||
{
|
||||
fprintf(stderr, "ERROR - Diff between intermediate buffer read/write pointers %d Buffer level recorded %d (diff %i)\n",
|
||||
calcBufferLevel, mBufferLevelInSamples, mBufferLevelInSamples - calcBufferLevel);
|
||||
}
|
||||
#endif
|
||||
|
||||
return mBufferLevelInSamples;
|
||||
}
|
||||
|
||||
|
||||
uint32_t IntermediateBuffer::getAvailableSpaceInSamplesInBuffer()
|
||||
{
|
||||
return mBufferSizeInSamples - getNumSamplesInBuffer();
|
||||
}
|
||||
|
||||
snd_pcm_uframes_t IntermediateBuffer::writeAlsaSamples(void *alsaBuffer, uint32_t numSamples, ssize_t bytesPerSample)
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mBufferMutex);
|
||||
|
||||
// Overrun protection
|
||||
int32_t samplesToWrite = numSamples;
|
||||
|
||||
if ((getNumSamplesInBuffer() + numSamples) > mBufferSizeInSamples)
|
||||
{
|
||||
samplesToWrite = mBufferSizeInSamples - getNumSamplesInBuffer();
|
||||
}
|
||||
|
||||
uint8_t *bufPtr = (uint8_t *)alsaBuffer;
|
||||
if (mWriteHead + samplesToWrite > mBufferSizeInSamples)
|
||||
{
|
||||
unsigned int firstBatchSize = mBufferSizeInSamples - mWriteHead;
|
||||
writeAlsaSamplesToBuffer(bufPtr, firstBatchSize, bytesPerSample);
|
||||
assert(mWriteHead == 0);
|
||||
writeAlsaSamplesToBuffer(bufPtr + (firstBatchSize * bytesPerSample), samplesToWrite - firstBatchSize, bytesPerSample);
|
||||
}
|
||||
else
|
||||
{
|
||||
writeAlsaSamplesToBuffer(bufPtr, samplesToWrite, bytesPerSample);
|
||||
}
|
||||
|
||||
mBufferLevelInSamples += samplesToWrite;
|
||||
mBufferLog->logWriteEvent(samplesToWrite, getNumSamplesInBuffer());
|
||||
|
||||
return samplesToWrite;
|
||||
}
|
||||
|
||||
snd_pcm_uframes_t IntermediateBuffer::readAlsaSamples(void *alsaBuffer, uint32_t numSamples, ssize_t bytesPerSample)
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mBufferMutex);
|
||||
|
||||
// Underrun protection
|
||||
if (numSamples > getNumSamplesInBuffer())
|
||||
{
|
||||
numSamples = getNumSamplesInBuffer();
|
||||
}
|
||||
|
||||
uint8_t *bufPtr = (uint8_t *)alsaBuffer;
|
||||
|
||||
if (mReadHead + numSamples > mBufferSizeInSamples)
|
||||
{
|
||||
unsigned int firstBatchSize = mBufferSizeInSamples - mReadHead;
|
||||
readAlsaSamplesFromBuffer(bufPtr, firstBatchSize, bytesPerSample);
|
||||
assert(mReadHead == 0);
|
||||
readAlsaSamplesFromBuffer(bufPtr + (firstBatchSize * bytesPerSample), numSamples - firstBatchSize, bytesPerSample);
|
||||
}
|
||||
else
|
||||
{
|
||||
readAlsaSamplesFromBuffer(bufPtr, numSamples, bytesPerSample);
|
||||
}
|
||||
mBufferLevelInSamples -= numSamples;
|
||||
mBufferLog->logReadEvent(numSamples, getNumSamplesInBuffer());
|
||||
|
||||
return numSamples;
|
||||
}
|
||||
|
||||
void IntermediateBuffer::writeAlsaSamplesToBuffer(uint8_t* buffer, uint32_t numSamples, ssize_t bytesPerSample)
|
||||
{
|
||||
memcpy((uint8_t *)(mBuffer) + (mWriteHead * bytesPerSample), buffer, bytesPerSample * numSamples);
|
||||
mWriteHead = (mWriteHead + numSamples) % mBufferSizeInSamples;
|
||||
}
|
||||
|
||||
void IntermediateBuffer::readAlsaSamplesFromBuffer(uint8_t* buffer, uint32_t numSamples, ssize_t bytesPerSample)
|
||||
{
|
||||
memcpy(buffer, (uint8_t *)mBuffer + (mReadHead * bytesPerSample), bytesPerSample * numSamples);
|
||||
mReadHead = (mReadHead + numSamples) % mBufferSizeInSamples;
|
||||
}
|
||||
|
||||
snd_pcm_uframes_t IntermediateBuffer::writeDepSamples(
|
||||
snd_pcm_format_t format,
|
||||
uint32_t numFrames,
|
||||
std::vector<const int32_t *> & danteRxChannels,
|
||||
uint32_t numAlsaChannels,
|
||||
uint32_t & danteRxHeadSamples,
|
||||
uint32_t samplesPerChannel)
|
||||
{
|
||||
uint32_t framesToWrite = numFrames;
|
||||
uint32_t framesToSkip = 0;
|
||||
static bool bufferFullLogState = false;
|
||||
|
||||
// Check for overrun
|
||||
uint32_t numSamples = numFrames * numAlsaChannels;
|
||||
if ((getNumSamplesInBuffer() + numSamples) > mBufferSizeInSamples)
|
||||
{
|
||||
framesToWrite = (mBufferSizeInSamples - getNumSamplesInBuffer()) / numAlsaChannels;
|
||||
framesToSkip = numFrames - framesToWrite;
|
||||
|
||||
if (framesToWrite == 0)
|
||||
{
|
||||
if (bufferFullLogState == false) // Limit the number of times that this message is displayed.
|
||||
{
|
||||
std::cerr << "WARNING: " << __FUNCTION__ << ": buffer overrun (ALSA hasn't read enough). Buffer full." << std::endl;
|
||||
bufferFullLogState = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "WARNING: " << __FUNCTION__ << ": buffer overrun (ALSA hasn't read enough). " <<
|
||||
"Skipping " << framesToSkip << " frames." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (framesToWrite != 0)
|
||||
{
|
||||
bufferFullLogState = false;
|
||||
}
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case SND_PCM_FORMAT_S16_LE:
|
||||
writeDepSamplesS16LE(framesToWrite, danteRxChannels, numAlsaChannels, danteRxHeadSamples, samplesPerChannel);
|
||||
break;
|
||||
case SND_PCM_FORMAT_S32_LE:
|
||||
writeDepSamplesS32LE(framesToWrite, danteRxChannels, numAlsaChannels, danteRxHeadSamples, samplesPerChannel);
|
||||
break;
|
||||
case SND_PCM_FORMAT_FLOAT_LE:
|
||||
writeDepSamplesFLOATLE(framesToWrite, danteRxChannels, numAlsaChannels, danteRxHeadSamples, samplesPerChannel);
|
||||
break;
|
||||
default:
|
||||
std::cerr << "unexpected format" << std::endl;
|
||||
}
|
||||
mBufferLevelInSamples += framesToWrite * numAlsaChannels;
|
||||
mBufferLog->logWriteEvent(framesToWrite * numAlsaChannels, getNumSamplesInBuffer());
|
||||
|
||||
if (framesToSkip)
|
||||
{
|
||||
// Skip the shared memory pointer past the samples we aren't going to use
|
||||
danteRxHeadSamples = (danteRxHeadSamples + framesToSkip) % samplesPerChannel;
|
||||
}
|
||||
|
||||
return framesToWrite;
|
||||
}
|
||||
|
||||
snd_pcm_uframes_t IntermediateBuffer::readDepSamples(
|
||||
snd_pcm_format_t format,
|
||||
uint32_t numFrames,
|
||||
std::vector<int32_t *> & danteTxChannels,
|
||||
uint32_t numAlsaChannels,
|
||||
uint32_t & danteTxHeadSamples,
|
||||
uint32_t samplesPerChannel)
|
||||
{
|
||||
uint32_t framesToRead = numFrames;
|
||||
uint32_t framesToSkip = 0;
|
||||
static bool bufferEmptyLogState = false;
|
||||
|
||||
// Underrun protection
|
||||
uint32_t numSamples = numFrames * numAlsaChannels;
|
||||
if (getNumSamplesInBuffer() < numSamples)
|
||||
{
|
||||
framesToRead = getNumSamplesInBuffer() / numAlsaChannels;
|
||||
framesToSkip = numFrames - framesToRead;
|
||||
|
||||
if (framesToRead == 0)
|
||||
{
|
||||
if (bufferEmptyLogState == false)
|
||||
{
|
||||
bufferEmptyLogState = true;
|
||||
std::cerr << "WARNING: " << __FUNCTION__ << ": buffer underrun (ALSA hasn't written enough). No frames in buffer." << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "WARNING: " << __FUNCTION__ << ": buffer underrun (ALSA hasn't written enough). " <<
|
||||
"Skipping " << framesToSkip << " frames." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (framesToRead != 0)
|
||||
{
|
||||
bufferEmptyLogState = false;
|
||||
}
|
||||
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case SND_PCM_FORMAT_S16_LE:
|
||||
readDepSamples16LE(framesToRead, danteTxChannels, numAlsaChannels, danteTxHeadSamples, samplesPerChannel);
|
||||
break;
|
||||
case SND_PCM_FORMAT_S32_LE:
|
||||
readDepSamplesS32LE(framesToRead, danteTxChannels, numAlsaChannels, danteTxHeadSamples, samplesPerChannel);
|
||||
break;
|
||||
case SND_PCM_FORMAT_FLOAT_LE:
|
||||
readDepSamplesFLOATLE(framesToRead, danteTxChannels, numAlsaChannels, danteTxHeadSamples, samplesPerChannel);
|
||||
break;
|
||||
default:
|
||||
std::cerr << "unexpected format" << std::endl;
|
||||
}
|
||||
mBufferLevelInSamples -= framesToRead * numAlsaChannels;
|
||||
mBufferLog->logReadEvent(framesToRead * numAlsaChannels, getNumSamplesInBuffer());
|
||||
|
||||
if (framesToSkip)
|
||||
{
|
||||
// Skip the shared memory pointer to provide zeroes for samples that we can't read
|
||||
// from the intermediate buffer
|
||||
danteTxHeadSamples = (danteTxHeadSamples + framesToSkip) % samplesPerChannel;
|
||||
}
|
||||
|
||||
return framesToRead;
|
||||
}
|
||||
|
||||
uint32_t IntermediateBuffer::writeDepSamplesS16LE(uint32_t numFrames, std::vector<const int32_t *> & danteRxChannels, uint32_t numAlsaChannels, uint32_t & danteRxHeadSamples, uint32_t samplesPerChannel)
|
||||
{
|
||||
for (unsigned int frame = 0; frame < numFrames; frame++)
|
||||
{
|
||||
for (unsigned int channel = 0; channel < numAlsaChannels; channel++)
|
||||
{
|
||||
int32_t sample32 = *(danteRxChannels[channel] + danteRxHeadSamples);
|
||||
*((int16_t *)(mBuffer) + mWriteHead) = (int16_t)(sample32 >> 16);
|
||||
mWriteHead++;
|
||||
}
|
||||
mWriteHead %= mBufferSizeInSamples;
|
||||
danteRxHeadSamples = (danteRxHeadSamples + 1) % samplesPerChannel;
|
||||
}
|
||||
return numFrames;
|
||||
}
|
||||
|
||||
uint32_t IntermediateBuffer::writeDepSamplesS32LE(uint32_t numFrames, std::vector<const int32_t *> & danteRxChannels, uint32_t numAlsaChannels, uint32_t & danteRxHeadSamples, uint32_t samplesPerChannel)
|
||||
{
|
||||
for (unsigned int frame = 0; frame < numFrames; frame++)
|
||||
{
|
||||
for (unsigned int channel = 0; channel < numAlsaChannels; channel++)
|
||||
{
|
||||
*(mBuffer + mWriteHead) = *(danteRxChannels[channel] + danteRxHeadSamples);
|
||||
mWriteHead++;
|
||||
}
|
||||
mWriteHead %= mBufferSizeInSamples;
|
||||
danteRxHeadSamples = (danteRxHeadSamples + 1) % samplesPerChannel;
|
||||
}
|
||||
return numFrames;
|
||||
}
|
||||
|
||||
uint32_t IntermediateBuffer::writeDepSamplesFLOATLE(uint32_t numFrames, std::vector<const int32_t *> & danteRxChannels, uint32_t numAlasChannels, uint32_t & danteRxHeadSamples, uint32_t samplesPerChannel)
|
||||
{
|
||||
float* floatBuffer = (float*)mBuffer;
|
||||
double doubleTemp;
|
||||
for (unsigned int frame = 0; frame < numFrames; frame++)
|
||||
{
|
||||
for (unsigned int channel = 0; channel < numAlasChannels; channel++)
|
||||
{
|
||||
doubleTemp = *(danteRxChannels[channel] + danteRxHeadSamples);
|
||||
doubleTemp /= (double) INT32_MAX;
|
||||
*(floatBuffer + mWriteHead) = (float) doubleTemp;
|
||||
mWriteHead++;
|
||||
}
|
||||
mWriteHead %= mBufferSizeInSamples;
|
||||
danteRxHeadSamples = (danteRxHeadSamples + 1) % samplesPerChannel;
|
||||
}
|
||||
return numFrames;
|
||||
}
|
||||
|
||||
uint32_t IntermediateBuffer::readDepSamples16LE(uint32_t numFrames, std::vector<int32_t *> & danteTxChannels, uint32_t numAlsaChannels, uint32_t & danteTxHeadSamples, uint32_t samplesPerChannel)
|
||||
{
|
||||
for (unsigned int frame = 0; frame < numFrames; frame++)
|
||||
{
|
||||
for (unsigned int channel = 0; channel < numAlsaChannels; channel++)
|
||||
{
|
||||
int16_t sample16 = *((int16_t *)(mBuffer) + mReadHead);
|
||||
int32_t sample32 = sample16 << 16;
|
||||
*(danteTxChannels[channel] + danteTxHeadSamples) = sample32;
|
||||
// Write silence to prevent audio looping if input goes away
|
||||
*((int16_t *)(mBuffer) + mReadHead) = 0;
|
||||
mReadHead++;
|
||||
}
|
||||
mReadHead %= mBufferSizeInSamples;
|
||||
danteTxHeadSamples = (danteTxHeadSamples + 1) % samplesPerChannel;
|
||||
}
|
||||
|
||||
return numFrames;
|
||||
}
|
||||
|
||||
uint32_t IntermediateBuffer::readDepSamplesS32LE(uint32_t numFrames, std::vector<int32_t *> & danteTxChannels, uint32_t numAlsaChannels, uint32_t & danteTxHeadSamples, uint32_t samplesPerChannel)
|
||||
{
|
||||
for (unsigned int frame = 0; frame < numFrames; frame++)
|
||||
{
|
||||
for (unsigned int channel = 0; channel < numAlsaChannels; channel++)
|
||||
{
|
||||
*(danteTxChannels[channel] + danteTxHeadSamples) = *(mBuffer + mReadHead);
|
||||
// Write silence to prevent audio looping if input goes away
|
||||
*(mBuffer + mReadHead) = 0;
|
||||
mReadHead++;
|
||||
}
|
||||
mReadHead %= mBufferSizeInSamples;
|
||||
danteTxHeadSamples = (danteTxHeadSamples + 1) % samplesPerChannel;
|
||||
}
|
||||
return numFrames;
|
||||
}
|
||||
|
||||
uint32_t IntermediateBuffer::readDepSamplesFLOATLE(uint32_t numFrames, std::vector<int32_t *> & danteTxChannels, uint32_t numAlsaChannels, uint32_t & danteTxHeadSamples, uint32_t samplesPerChannel)
|
||||
{
|
||||
float* floatBuffer = (float*) mBuffer;
|
||||
for (unsigned int frame = 0; frame < numFrames; frame++)
|
||||
{
|
||||
for (unsigned int channel = 0; (channel < numAlsaChannels) && (mReadHead != mWriteHead); channel++)
|
||||
{
|
||||
*(danteTxChannels[channel] + danteTxHeadSamples) = (uint32_t) ( *(floatBuffer + mReadHead) * INT32_MAX );
|
||||
// Write silence to prevent audio looping if input goes away
|
||||
*(mBuffer + mReadHead) = 0;
|
||||
mReadHead++;
|
||||
}
|
||||
mReadHead %= mBufferSizeInSamples;
|
||||
danteTxHeadSamples = (danteTxHeadSamples + 1) % samplesPerChannel;
|
||||
}
|
||||
return numFrames;
|
||||
}
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,316 @@
|
||||
//
|
||||
// Copyright © 2023 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.
|
||||
//
|
||||
// dsoundcard_logging.hpp
|
||||
// Generates timestamped stats log in csv format which can be imported into a spreadsheet to examine buffer levels and thread timings.
|
||||
//
|
||||
#include <pthread.h>
|
||||
|
||||
#include "dsoundcard_logging.hpp"
|
||||
#include "dante/Buffers.hpp"
|
||||
|
||||
#if ((__ENABLE_BUFFER_LOGGING == 1) || (__ENABLE_FUNCTION_TRACE_LOGGING == 1))
|
||||
static uint64_t getCurrTimeInMicroseconds() {
|
||||
return Dante::getMonotonicValue() / 1000;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if (__ENABLE_FUNCTION_TRACE_LOGGING == 1)
|
||||
DEPSoundcardFunctionLog functionLog("/tmp/dep_soundcard_function_log.csv");
|
||||
#endif
|
||||
|
||||
|
||||
#if (__ENABLE_BUFFER_LOGGING == 1)
|
||||
|
||||
#define SAMPLE_RATE_LOGGING_PERIOD_US (60 * 1000 * 1000)
|
||||
|
||||
void* bufferLogDumpThreadFunction(void* arg) {
|
||||
|
||||
DEPSoundcardBufferLog *bufferLog = (DEPSoundcardBufferLog *)arg;
|
||||
|
||||
if(bufferLog->mOutputFile.tellp() > (25 * 1024 * 1024)) // Limit file size to 25M
|
||||
{
|
||||
bufferLog->mOutputFile.close();
|
||||
bufferLog->open();
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < bufferLog->mLogEntries.size(); i++)
|
||||
{
|
||||
bufferLog->mOutputFile
|
||||
<< bufferLog->mLogEntries[i].timestamp << ","
|
||||
<< bufferLog->mLogEntries[i].threadId << ","
|
||||
<< bufferLog->mLogEntries[i].samplesWritten << ","
|
||||
<< bufferLog->mLogEntries[i].writeOperation << ","
|
||||
<< bufferLog->mLogEntries[i].newBufferLevel << ","
|
||||
<< (bufferLog->mLogEntries[i].newBufferLevel * 1000) / 96 // ms - 48 kHz, 2 channels
|
||||
<< std::endl;
|
||||
}
|
||||
bufferLog->mLogEntries.clear();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DEPSoundcardBufferLog::DEPSoundcardBufferLog(const char *logFileName)
|
||||
: mOutputFile(), mLogStartTime(0), mFirstPthreadId(0), mLogFileName(logFileName),
|
||||
mSamplesWritten(0), mSamplesRead(0),
|
||||
mPrevWriteSampleRateLogTime(0), mPrevReadSampleRateLogTime(0)
|
||||
|
||||
{
|
||||
mLogStartTime = getCurrTimeInMicroseconds();
|
||||
|
||||
open();
|
||||
mOutputFile << "0, 0, 0, 0" << std::endl;
|
||||
}
|
||||
|
||||
DEPSoundcardBufferLog::~DEPSoundcardBufferLog()
|
||||
{
|
||||
mOutputFile.close();
|
||||
}
|
||||
|
||||
void DEPSoundcardBufferLog::open()
|
||||
{
|
||||
mOutputFile.open(mLogFileName);
|
||||
mOutputFile << "Timestamp (us), Thread, Samples, Write operation, Buffer level (samples), Buffer level (us)" << std::endl;
|
||||
}
|
||||
|
||||
void DEPSoundcardBufferLog::logWriteEvent(const uint32_t bufferLevelChange, const uint32_t newBufferLevel)
|
||||
{
|
||||
mSamplesWritten += bufferLevelChange;
|
||||
logEvent(bufferLevelChange, newBufferLevel, true);
|
||||
}
|
||||
|
||||
void DEPSoundcardBufferLog::logReadEvent(const uint32_t bufferLevelChange, const uint32_t newBufferLevel)
|
||||
{
|
||||
mSamplesRead += bufferLevelChange;
|
||||
logEvent(bufferLevelChange, newBufferLevel, false);
|
||||
}
|
||||
|
||||
void DEPSoundcardBufferLog::logEvent(const uint32_t bufferLevelChange, const uint32_t newBufferLevel, bool write)
|
||||
{
|
||||
uint64_t buffTime = getCurrTimeInMicroseconds() - mLogStartTime;
|
||||
pthread_t threadId = pthread_self();
|
||||
|
||||
if (mFirstPthreadId == 0)
|
||||
{
|
||||
mFirstPthreadId = threadId;
|
||||
}
|
||||
|
||||
struct dsoundcardBufferLogEntry logEntry;
|
||||
|
||||
logEntry.timestamp = buffTime;
|
||||
logEntry.threadId = (threadId == mFirstPthreadId);
|
||||
logEntry.writeOperation = write;
|
||||
logEntry.samplesWritten = bufferLevelChange;
|
||||
logEntry.newBufferLevel = newBufferLevel;
|
||||
|
||||
mLogEntries.push_back(logEntry);
|
||||
|
||||
if (mLogEntries.size() == (MaxNumLogEntries - 1000)) // Kick off a low priority thread to print out the log entries
|
||||
{
|
||||
pthread_t thread;
|
||||
struct sched_param params;
|
||||
params.sched_priority = sched_get_priority_min(SCHED_OTHER); // Set the priority value here
|
||||
pthread_create(&thread, NULL, bufferLogDumpThreadFunction, this);
|
||||
pthread_setschedparam(thread, SCHED_OTHER, ¶ms);
|
||||
}
|
||||
|
||||
// Note that the sample rates displayed will be the total for all channels - i.e.
|
||||
// stereo 48kHz = 96000
|
||||
if (write)
|
||||
{
|
||||
displayWriteSampleRates(buffTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
displayReadSampleRates(buffTime);
|
||||
}
|
||||
}
|
||||
|
||||
void DEPSoundcardBufferLog::displayWriteSampleRates(uint64_t buffTime)
|
||||
{
|
||||
if (mPrevWriteSampleRateLogTime == 0)
|
||||
{
|
||||
mPrevWriteSampleRateLogTime = buffTime;
|
||||
}
|
||||
else if ((buffTime - mPrevWriteSampleRateLogTime) >= SAMPLE_RATE_LOGGING_PERIOD_US)
|
||||
{
|
||||
double measurementTime = (double)(buffTime - mPrevWriteSampleRateLogTime);
|
||||
double sampleRate = ((double)mSamplesWritten * 1000000.0) / measurementTime;
|
||||
mPrevWriteSampleRateLogTime = buffTime;
|
||||
mSamplesWritten = 0;
|
||||
|
||||
std::cerr << buffTime / (1000 * 1000) << "s Write samplerate " << sampleRate << " samples/sec (all channs)" << std::endl;
|
||||
}
|
||||
}
|
||||
void DEPSoundcardBufferLog::displayReadSampleRates(uint64_t buffTime)
|
||||
{
|
||||
if (mPrevReadSampleRateLogTime == 0)
|
||||
{
|
||||
mPrevReadSampleRateLogTime = buffTime;
|
||||
}
|
||||
else if ((buffTime - mPrevReadSampleRateLogTime) >= SAMPLE_RATE_LOGGING_PERIOD_US)
|
||||
{
|
||||
double measurementTime = (double)(buffTime - mPrevReadSampleRateLogTime);
|
||||
double sampleRate = ((double)mSamplesRead * 1000000.0) / measurementTime;
|
||||
mPrevReadSampleRateLogTime = buffTime;
|
||||
mSamplesRead = 0;
|
||||
|
||||
std::cerr << buffTime / (1000 * 1000) << "s Read samplerate " << sampleRate << " samples/sec (all channs)" << std::endl;
|
||||
}
|
||||
}
|
||||
#endif // __ENABLE_BUFFER_LOGGING == 1
|
||||
|
||||
|
||||
#if (__ENABLE_FUNCTION_TRACE_LOGGING == 1)
|
||||
|
||||
void* functionLogDumpThreadFunction(void* arg) {
|
||||
|
||||
DEPSoundcardFunctionLog *functionLog = (DEPSoundcardFunctionLog *)arg;
|
||||
|
||||
if (functionLog->mLogEntries.size() >= functionLog->MaxNumLogEntries)
|
||||
{
|
||||
std::cerr << "Potential function log overflow" << std::endl;
|
||||
}
|
||||
|
||||
if(functionLog->mOutputFile.tellp() > (25 * 1024 * 1024)) // Limit file size to 25M
|
||||
{
|
||||
functionLog->mOutputFile.close();
|
||||
functionLog->open();
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < functionLog->mLogEntries.size(); i++)
|
||||
{
|
||||
bool functionStartMarker = functionLog->mLogEntries[i].duration == (uint64_t)(-1);
|
||||
|
||||
if(functionStartMarker)
|
||||
{
|
||||
functionLog->mLogEntries[i].duration = 0;
|
||||
}
|
||||
|
||||
functionLog->mOutputFile
|
||||
<< functionLog->mLogEntries[i].timestamp << ","
|
||||
<< functionLog->mLogEntries[i].functionName << ","
|
||||
<< functionStartMarker << ","
|
||||
<< functionLog->mLogEntries[i].duration
|
||||
<< std::endl;
|
||||
}
|
||||
functionLog->mLogEntries.clear();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DEPSoundcardFunctionLog::DEPSoundcardFunctionLog(const char *logFileName)
|
||||
: mOutputFile(), mLogStartTime(0), mLogFileName(logFileName)
|
||||
{
|
||||
mLogStartTime = getCurrTimeInMicroseconds();
|
||||
|
||||
open();
|
||||
mOutputFile << "0, null" << std::endl;
|
||||
}
|
||||
|
||||
DEPSoundcardFunctionLog::~DEPSoundcardFunctionLog()
|
||||
{
|
||||
// Dump the remaining contents of the log
|
||||
functionLogDumpThreadFunction(this);
|
||||
mOutputFile.close();
|
||||
}
|
||||
|
||||
void DEPSoundcardFunctionLog::open()
|
||||
{
|
||||
mOutputFile.open(mLogFileName);
|
||||
mOutputFile << "Timestamp (us), Function, Start / End, Duration (us)" << std::endl;
|
||||
}
|
||||
|
||||
|
||||
uint64_t DEPSoundcardFunctionLog::logFunctionStart(const char *functionName)
|
||||
{
|
||||
uint64_t buffTime = getCurrTimeInMicroseconds() - mLogStartTime;
|
||||
|
||||
if(mLogEntries.size() < MaxNumLogEntries)
|
||||
{
|
||||
struct dsoundcardFunctionLogEntry logEntry;
|
||||
|
||||
logEntry.timestamp = buffTime;
|
||||
strncpy(logEntry.functionName, functionName, MaxFunctionNameLength);
|
||||
logEntry.duration = (uint64_t)(-1);
|
||||
mLogEntries.push_back(logEntry);
|
||||
}
|
||||
|
||||
if (mLogEntries.size() == (MaxNumLogEntries / 2)) // Kick off a low priority thread to print out the log entries
|
||||
{
|
||||
pthread_t thread;
|
||||
struct sched_param params;
|
||||
params.sched_priority = sched_get_priority_min(SCHED_OTHER); // Set the priority value here
|
||||
pthread_create(&thread, NULL, functionLogDumpThreadFunction, this);
|
||||
pthread_setschedparam(thread, SCHED_OTHER, ¶ms);
|
||||
}
|
||||
return buffTime;
|
||||
}
|
||||
|
||||
void DEPSoundcardFunctionLog::logFunctionEnd(const char *functionName, uint64_t startTime)
|
||||
{
|
||||
uint64_t buffTime = getCurrTimeInMicroseconds() - mLogStartTime;
|
||||
uint64_t duration = buffTime - startTime;
|
||||
|
||||
if(mLogEntries.size() < MaxNumLogEntries)
|
||||
{
|
||||
struct dsoundcardFunctionLogEntry logEntry;
|
||||
|
||||
logEntry.timestamp = buffTime;
|
||||
strncpy(logEntry.functionName, functionName, MaxFunctionNameLength);
|
||||
logEntry.duration = duration;
|
||||
mLogEntries.push_back(logEntry);
|
||||
}
|
||||
}
|
||||
#endif // __ENABLE_FUNCTION_TRACE_LOGGING == 1
|
||||
|
||||
//
|
||||
// Copyright © 2023 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,431 @@
|
||||
//
|
||||
// Copyright © 2021 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.
|
||||
//
|
||||
// dtest.cpp
|
||||
// Audio test example
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
#include "dante/Priority.hpp"
|
||||
#include "versions.h"
|
||||
#include <assert.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#ifdef WIN32
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "3rd_party/sinewave.hpp"
|
||||
#include "3rd_party/pink.hpp"
|
||||
|
||||
#if TRACEPOINTS
|
||||
#include <sys/sdt.h>
|
||||
#endif
|
||||
|
||||
#define ZERO_INDEXED(n) ( (n) - 1)
|
||||
#define ONE_INDEXED(n) ( (n) + 1)
|
||||
|
||||
#define MAX_SAMPLE_PROCESS_BLOCK 4096
|
||||
|
||||
#define SIGNAL_GENERATOR_DEFAULT 'n'
|
||||
#define OUTPUT_CHANNEL_DEFAULT 0
|
||||
#define FREQUENCY_DEFAULT 440.0
|
||||
#define SECONDS_PER_CHANNEL_DEFAULT 1.0
|
||||
#define AMPLITUDE_DEFAULT 0.5
|
||||
|
||||
static bool g_running = true;
|
||||
|
||||
static void signalHandler(int sig)
|
||||
{
|
||||
(void) sig;
|
||||
g_running = false;
|
||||
signal(SIGINT, signalHandler);
|
||||
}
|
||||
|
||||
class AudioTest
|
||||
{
|
||||
public:
|
||||
AudioTest(Dante::Buffers & buffers, int txLatencySamples, char signalGenerator, int outputChannel,
|
||||
double frequency, double secondsPerChannel, double amplitude)
|
||||
: mBuffers(buffers), mTxLatencySamples(txLatencySamples), mOutputChannel(outputChannel), mFrequency(frequency),
|
||||
mSecondsPerChannel(secondsPerChannel), mAmplitude(amplitude), mCurrentChannel(), mSamplesPerPeriod(),
|
||||
mSamplesPerChannel(), mDanteTxHeadSamples(), mDanteRxHeadSamples(), mDanteTxChannels(), mDanteRxChannels()
|
||||
{
|
||||
if (signalGenerator =='n')
|
||||
{
|
||||
init_pink_noise();
|
||||
generator_ptr = &get_pink_samples;
|
||||
}
|
||||
else
|
||||
{
|
||||
generator_ptr = &get_tone_samples;
|
||||
}
|
||||
}
|
||||
|
||||
void work(unsigned int numPeriods)
|
||||
{
|
||||
unsigned int numSamples = numPeriods * mSamplesPerPeriod;
|
||||
|
||||
// For extreme stalls (e.g. breakpoints while debugging) just jump to catch up
|
||||
if (numSamples > mSamplesPerChannel)
|
||||
{
|
||||
numSamples = numSamples % mSamplesPerChannel;
|
||||
}
|
||||
|
||||
// Unwrap the Dante TX loop
|
||||
if (mDanteTxHeadSamples + numSamples > mSamplesPerChannel)
|
||||
{
|
||||
unsigned int n1 = mSamplesPerChannel - mDanteTxHeadSamples;
|
||||
unsigned int n2 = numSamples - n1;
|
||||
pullSamplesFromGenerator(n1);
|
||||
assert(mDanteTxHeadSamples == mSamplesPerChannel);
|
||||
mDanteTxHeadSamples = 0;
|
||||
pullSamplesFromGenerator(n2);
|
||||
}
|
||||
else
|
||||
{
|
||||
pullSamplesFromGenerator(numSamples);
|
||||
}
|
||||
|
||||
#if TRACEPOINTS
|
||||
DTRACE_PROBE1(audioTest, samplepercycle, numSamples);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Called when there are changes to the buffer header epoch or samplerate
|
||||
// values.
|
||||
void reset()
|
||||
{
|
||||
auto metadata = mBuffers.getHeader();
|
||||
mSamplesPerPeriod = metadata->time.samples_per_period;
|
||||
mSamplesPerChannel = metadata->audio.samples_per_channel;
|
||||
|
||||
mDanteTxChannels.resize(mBuffers.getHeader()->audio.num_tx_channels);
|
||||
for (unsigned int i = 0; i < mDanteTxChannels.size(); i++)
|
||||
{
|
||||
mDanteTxChannels[i] = (int32_t *) mBuffers.getDanteTxChannel(i);
|
||||
}
|
||||
mDanteRxChannels.resize(mBuffers.getHeader()->audio.num_rx_channels);
|
||||
for (unsigned int i = 0; i < mDanteRxChannels.size(); i++)
|
||||
{
|
||||
mDanteRxChannels[i] = (const int32_t *) mBuffers.getDanteRxChannel(i);
|
||||
}
|
||||
|
||||
mDanteRxHeadSamples = (unsigned int) ((metadata->time.period_count*mSamplesPerPeriod) % mSamplesPerChannel);
|
||||
mDanteTxHeadSamples = (unsigned int) ((metadata->time.period_count*mSamplesPerPeriod + mTxLatencySamples) % mSamplesPerChannel);
|
||||
|
||||
switch(metadata->audio.encoding)
|
||||
{
|
||||
case 8:
|
||||
mMaxEncodingValue = (double)0x7F;
|
||||
break;
|
||||
case 16:
|
||||
mMaxEncodingValue = (double)0x7FFF;
|
||||
break;
|
||||
case 24:
|
||||
mMaxEncodingValue = (double)0x7FFFFF;
|
||||
break;
|
||||
case 32:
|
||||
mMaxEncodingValue = (double)0x7FFFFFFF;
|
||||
break;
|
||||
default:
|
||||
std::cout << "Error encoding value unexpected" << std::endl;
|
||||
g_running = false;
|
||||
}
|
||||
|
||||
if (mOutputChannel >= (int) mDanteTxChannels.size())
|
||||
{
|
||||
std::cout << "The given channel index '" << mOutputChannel << "' was greater than the number of dep transmitter channels" << std::endl;
|
||||
g_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Dante::Buffers & mBuffers;
|
||||
|
||||
unsigned int mTxLatencySamples;
|
||||
int mOutputChannel;
|
||||
double mFrequency;
|
||||
double mSecondsPerChannel;
|
||||
double mAmplitude;
|
||||
unsigned int mCurrentChannel;
|
||||
unsigned int mSamplesPerPeriod;
|
||||
unsigned int mSamplesPerChannel;
|
||||
unsigned int mDanteTxHeadSamples;
|
||||
unsigned int mDanteRxHeadSamples;
|
||||
std::vector<int32_t *> mDanteTxChannels;
|
||||
std::vector<const int32_t *> mDanteRxChannels;
|
||||
|
||||
int32_t sampleBuffer[MAX_SAMPLE_PROCESS_BLOCK];
|
||||
uint32_t mSamplesSoFar = 0;
|
||||
double mMaxEncodingValue;
|
||||
|
||||
void (*generator_ptr)(double, double, unsigned int, double, double, int32_t*, uint16_t, double);
|
||||
|
||||
void pullSamplesFromGenerator(unsigned int numSamples)
|
||||
{
|
||||
auto * metadata = &mBuffers.getHeader()->audio;
|
||||
|
||||
unsigned int samplesThisCycle;
|
||||
|
||||
// -1 indicates we should cycle through available channels
|
||||
if (mOutputChannel == -1)
|
||||
{
|
||||
unsigned int nextChannel = (unsigned int)( mSamplesSoFar / ( (double)metadata->sample_rate * mSecondsPerChannel ) ) % mDanteTxChannels.size();
|
||||
if (nextChannel != mCurrentChannel)
|
||||
{
|
||||
mCurrentChannel = nextChannel;
|
||||
std::cout << "Now playing over channel " << ONE_INDEXED(mCurrentChannel) << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mCurrentChannel = mOutputChannel;
|
||||
}
|
||||
|
||||
do {
|
||||
samplesThisCycle = numSamples > MAX_SAMPLE_PROCESS_BLOCK ? MAX_SAMPLE_PROCESS_BLOCK : numSamples;
|
||||
numSamples -= samplesThisCycle;
|
||||
|
||||
double time = ((double) mSamplesSoFar) / ((double) metadata->sample_rate);
|
||||
double timedelta = ((double) 1.0) / ((double) metadata->sample_rate);
|
||||
|
||||
(*generator_ptr)(time, timedelta, samplesThisCycle, mFrequency, mAmplitude, sampleBuffer, metadata->encoding, mMaxEncodingValue);
|
||||
|
||||
unsigned int numBytes = samplesThisCycle * sizeof(int32_t);
|
||||
memcpy(mDanteTxChannels[mCurrentChannel] + mDanteTxHeadSamples, sampleBuffer, numBytes);
|
||||
|
||||
mDanteTxHeadSamples += samplesThisCycle;
|
||||
mSamplesSoFar += samplesThisCycle;
|
||||
} while (numSamples > 0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#ifdef WIN32
|
||||
#define PLAYBACK_DEFAULT_TX_LATENCY_SAMPLES 480 // 10ms @ 48K
|
||||
#else
|
||||
#define PLAYBACK_DEFAULT_TX_LATENCY_SAMPLES 48 // 1ms @ 48K
|
||||
#endif
|
||||
|
||||
#define SHM_DEFAULT_NAME "DanteEP"
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
Dante::Buffers buffers;
|
||||
Dante::Runner runner(buffers);
|
||||
std::string shm = SHM_DEFAULT_NAME;
|
||||
int txLatencySamples = PLAYBACK_DEFAULT_TX_LATENCY_SAMPLES;
|
||||
|
||||
char signalGenerator = SIGNAL_GENERATOR_DEFAULT;
|
||||
int outputChannel = OUTPUT_CHANNEL_DEFAULT;
|
||||
double secondsPerChannel = SECONDS_PER_CHANNEL_DEFAULT;
|
||||
double frequency = FREQUENCY_DEFAULT;
|
||||
double amplitude = AMPLITUDE_DEFAULT;
|
||||
|
||||
#ifdef WIN32
|
||||
if (argc > 1)
|
||||
{
|
||||
shm = argv[1];
|
||||
}
|
||||
#else
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "l:s:f:a:t:c:g:hv")) != -1)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 's':
|
||||
shm = optarg;
|
||||
break;
|
||||
case 'l':
|
||||
txLatencySamples = atoi(optarg);
|
||||
break;
|
||||
case 'g':
|
||||
signalGenerator = optarg[0];
|
||||
if (signalGenerator != 'n' && signalGenerator != 't')
|
||||
{
|
||||
std::cout << "Signal generator must be one of 'n' (noise), 't' (tone)" <<
|
||||
std::endl;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'c':
|
||||
outputChannel = atoi(optarg);
|
||||
if (outputChannel < 0)
|
||||
{
|
||||
std::cout << "Output channel must be greater than or equal to 0." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
frequency = atof(optarg);
|
||||
if (frequency < 30.0 || frequency > 8000.0)
|
||||
{
|
||||
std::cout << "Tone frequencies must be in the range 30-8000hz." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
secondsPerChannel = atof(optarg);
|
||||
if (secondsPerChannel < 0)
|
||||
{
|
||||
std::cout << "Seconds per channel must be greater than 0." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'a':
|
||||
amplitude = atof(optarg);
|
||||
if (amplitude < 0 || amplitude > 1)
|
||||
{
|
||||
std::cout << "Amplitude must be in the range 0-1." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case 'v':
|
||||
std::cout << getVersionInfo() << std::endl;
|
||||
return 0;
|
||||
case 'h':
|
||||
default:
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout <<
|
||||
"\t-h Show help" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-l Playback Tx Latency (# samples) - default is " << PLAYBACK_DEFAULT_TX_LATENCY_SAMPLES <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-s Shared memory name to use - default is " << SHM_DEFAULT_NAME <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-g Signal generator - default is " << SIGNAL_GENERATOR_DEFAULT << std::endl <<
|
||||
"\t\t n -> noise" << std::endl <<
|
||||
"\t\t t -> tone" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-c Output channel - default is " << OUTPUT_CHANNEL_DEFAULT << std::endl <<
|
||||
"\t\t Setting the output channel to '0' cycles through the available channels" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-f Tone frequency (Hz) - default is " << FREQUENCY_DEFAULT << "Hz" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-t Time spent on each channel when cycling (seconds) - default is " << SECONDS_PER_CHANNEL_DEFAULT << "s" <<
|
||||
std::endl;
|
||||
std::cout <<
|
||||
"\t-a Amplitude of tone - default is " << AMPLITUDE_DEFAULT <<
|
||||
std::endl;
|
||||
std::cout << "\t-v Print out version" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
std::cout << "Using \"" << shm << "\" as shared memory" << std::endl;
|
||||
std::cout << "Using " << txLatencySamples << " samples as Tx Latency" << std::endl;
|
||||
std::cout << "Signal generator: " << signalGenerator << std::endl;
|
||||
std::cout << "Output channel: " << outputChannel << std::endl;
|
||||
std::cout << "Frequency: " << frequency << "Hz" << std::endl;
|
||||
std::cout << "Seconds per channel: " << secondsPerChannel << "s" << std::endl;
|
||||
std::cout << "Amplitude: " << amplitude << std::endl;
|
||||
|
||||
#endif
|
||||
|
||||
AudioTest audioTest(buffers, txLatencySamples, signalGenerator, ZERO_INDEXED(outputChannel), frequency, secondsPerChannel, amplitude);
|
||||
|
||||
setDantePriority(argv[0]);
|
||||
|
||||
signal(SIGINT, signalHandler);
|
||||
|
||||
int lastConnectionResult = 0;
|
||||
while (g_running)
|
||||
{
|
||||
if (!lastConnectionResult)
|
||||
{
|
||||
std::cerr << "Connecting..." << std::endl;
|
||||
}
|
||||
int result = buffers.connect(shm, false);
|
||||
if (result)
|
||||
{
|
||||
if (result != lastConnectionResult)
|
||||
{
|
||||
lastConnectionResult = result;
|
||||
std::cerr << "Error connecting to shared memory: " << Dante::SharedMemory::getErrorMessage(result) << std::endl;
|
||||
std::cerr << "Trying to connect again..." << std::endl;
|
||||
}
|
||||
#ifdef WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
lastConnectionResult = 0;
|
||||
std::cerr << "Connected" << std::endl;
|
||||
|
||||
auto _work = [&audioTest](unsigned int numPeriods) { audioTest.work(numPeriods); };
|
||||
auto _reset = [&audioTest]() { audioTest.reset(); };
|
||||
runner.setActiveChangedFn([](bool active) {
|
||||
std::cerr << (active ? "Activated" : "Deactivated") << std::endl;
|
||||
});
|
||||
runner.run(g_running, _work, _reset);
|
||||
|
||||
std::cerr << "Disconnecting..." << std::endl;
|
||||
buffers.disconnect();
|
||||
std::cerr << "Disconnected" << std::endl;
|
||||
}
|
||||
|
||||
cleanupDantePriority();
|
||||
|
||||
return 0;
|
||||
}
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,259 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// DanteTrace.cpp
|
||||
// Audio trace example
|
||||
//
|
||||
#include "dante/Buffers.hpp"
|
||||
#include "versions.h"
|
||||
#include <signal.h>
|
||||
#include <iostream>
|
||||
#ifdef WIN32
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static bool g_running = true;
|
||||
|
||||
static void signal_handler(int sig)
|
||||
{
|
||||
(void) sig;
|
||||
g_running = false;
|
||||
signal(SIGINT, signal_handler);
|
||||
}
|
||||
|
||||
static void traceMetadata(const volatile Dante::buffer_header_t & m)
|
||||
{
|
||||
std::cerr
|
||||
<< "METADATA"
|
||||
<< " magic=0x" << std::hex << m.metadata.magic_marker << std::dec
|
||||
<< " buffer_length=" << m.metadata.buffer_length
|
||||
<< " header_length=" << m.metadata.metadata_header_length
|
||||
<< " flags=0x" << std::hex << m.metadata.flags << std::dec
|
||||
<< " channel0_offset_bytes=" << m.metadata.first_tx_channel_offset_bytes << "/" << m.metadata.first_rx_channel_offset_bytes
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
static void traceLayout(const volatile Dante::buffer_header_t & m)
|
||||
{
|
||||
std::cerr
|
||||
<< "AUDIO"
|
||||
<< " samplerate=" << m.audio.sample_rate
|
||||
<< " encoding=" << m.audio.encoding
|
||||
<< " samples/channel=" << m.audio.samples_per_channel
|
||||
<< " bytes/channels=" << m.audio.bytes_per_channel
|
||||
<< " num_channels=" << m.audio.num_tx_channels << "/" << m.audio.num_rx_channels
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
static void traceTime(const volatile Dante::buffer_header_t & m)
|
||||
{
|
||||
std::cerr
|
||||
<< "TIME"
|
||||
<< " epoch=" << m.time.epoch_seconds << "." << m.time.epoch_samples
|
||||
<< " reset_count=" << m.metadata.reset_count << " samples_per_period=" << m.time.samples_per_period
|
||||
<< " period_count=" << m.time.period_count << " clock_drift_ppb=" << m.time.clock_drift_ppb
|
||||
<< " monotonic=" << m.time.monotonic
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
|
||||
static void traceNetworkMonotonicTime(const volatile Dante::buffer_header_t & m, uint32_t samplerate, unsigned int intervalMs)
|
||||
{
|
||||
static uint64_t lastNetwork = 0;
|
||||
static uint64_t lastMonotonic = 0;
|
||||
|
||||
static uint64_t rate = 0;
|
||||
#ifdef WIN32
|
||||
{
|
||||
LARGE_INTEGER freq;
|
||||
QueryPerformanceFrequency(&freq);
|
||||
rate = freq.QuadPart;
|
||||
}
|
||||
#else
|
||||
{
|
||||
rate = 1000000000;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Collect the network and monotonic time from the
|
||||
// memory buffers. If DEP is currently updating these
|
||||
// values, DANTE_BUFFERS_FLAG__TIMING_UPDATE_SYNC will be set.
|
||||
// This is one method to capture a consistent snapshot of these values.
|
||||
uint64_t network = 0;
|
||||
uint64_t monotonic = 0;
|
||||
bool consistent = false;
|
||||
while (!consistent)
|
||||
{
|
||||
network = m.time.period_count * m.time.samples_per_period;
|
||||
monotonic = m.time.monotonic;
|
||||
|
||||
Dante::memory_barrier_acquire();
|
||||
if (m.metadata.flags & DANTE_BUFFERS_FLAG__TIMING_UPDATE_SYNC)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Dante::memory_barrier_acquire();
|
||||
|
||||
if (network == m.time.period_count * m.time.samples_per_period &&
|
||||
monotonic == m.time.monotonic)
|
||||
{
|
||||
consistent = true;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t monotonicDelta = monotonic - lastMonotonic;
|
||||
double monotonicDeltaNs = (((double) monotonicDelta) * 1000000000.0) / ((double) rate);
|
||||
|
||||
if (monotonicDelta > ((double) intervalMs) * 1000000.0)
|
||||
{
|
||||
uint64_t networkDelta = (network - lastNetwork);
|
||||
if (samplerate != 0)
|
||||
{
|
||||
double networkDeltaNs = (((double) networkDelta) * 1000000000.0) / ((double) samplerate);
|
||||
double ratio = monotonicDeltaNs / networkDeltaNs;
|
||||
std::cerr << "network time in samples " << network << " (" << networkDeltaNs << "ns delta)"
|
||||
<< " monotonic counter " << monotonic << " (" << monotonicDeltaNs << "ns delta)"
|
||||
<< " ratio=" << ratio << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note that if samplerate is zero in the header, DEP audio will be muted
|
||||
std::cerr << "network time in samples " << network << " (time in ns unknown)"
|
||||
<< " monotonic counter " << monotonic << "ns (" << monotonicDeltaNs << "ns delta)"
|
||||
<< " Samplerate unavailable" << std::endl;
|
||||
}
|
||||
lastNetwork = network;
|
||||
lastMonotonic = monotonic;
|
||||
}
|
||||
}
|
||||
|
||||
static void work(Dante::Buffers & buffers, unsigned int numPeriods, uint32_t samplerate, unsigned int interval)
|
||||
{
|
||||
traceNetworkMonotonicTime(*buffers.getHeader(), samplerate, interval);
|
||||
}
|
||||
|
||||
static void reset(Dante::Buffers & buffers, uint32_t *samplerate)
|
||||
{
|
||||
std::cerr << "-----------------------------------------" << std::endl;
|
||||
*samplerate = buffers.getHeader()->audio.sample_rate;
|
||||
traceMetadata(*buffers.getHeader());
|
||||
traceLayout(*buffers.getHeader());
|
||||
traceTime(*buffers.getHeader());
|
||||
}
|
||||
|
||||
// Sample at 10Hz
|
||||
#define SAMPLING_INTERVAL_MS 100
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "v")) != -1) {
|
||||
switch (opt) {
|
||||
case 'v':
|
||||
std::cout << getVersionInfo() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Dante::Buffers buffers;
|
||||
Dante::Runner runner(buffers);
|
||||
|
||||
uint32_t samplerate = 0;
|
||||
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
int lastConnectionResult = 0;
|
||||
while (g_running)
|
||||
{
|
||||
if (!lastConnectionResult)
|
||||
{
|
||||
std::cerr << "Connecting..." << std::endl;
|
||||
}
|
||||
int result = buffers.connect("DanteEP", false);
|
||||
if (result)
|
||||
{
|
||||
if (result != lastConnectionResult)
|
||||
{
|
||||
lastConnectionResult = result;
|
||||
std::cerr << "Error connecting to shared memory: " << Dante::SharedMemory::getErrorMessage(result) << std::endl;
|
||||
std::cerr << "Trying to connect again..." << std::endl;
|
||||
}
|
||||
#ifdef WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
sleep(1);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
lastConnectionResult = 0;
|
||||
std::cerr << "Connected" << std::endl;
|
||||
traceMetadata(*buffers.getHeader());
|
||||
traceLayout(*buffers.getHeader());
|
||||
traceTime(*buffers.getHeader());
|
||||
|
||||
auto _work = [&buffers, &samplerate](unsigned int numPeriods) { work(buffers, numPeriods, samplerate, SAMPLING_INTERVAL_MS); };
|
||||
auto _reset = [&buffers, &samplerate]() { reset(buffers, &samplerate); };
|
||||
runner.setActiveChangedFn([](bool active) {
|
||||
std::cerr << (active ? "Activated" : "Deactivated") << std::endl;
|
||||
});
|
||||
runner.run(g_running, _work, _reset);
|
||||
|
||||
std::cerr << "Disconnecting..." << std::endl;
|
||||
buffers.disconnect();
|
||||
std::cerr << "Disconnected" << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
//
|
||||
// Copyright © 2020 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,41 @@
|
||||
set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES TOOLCHAINS_DIR)
|
||||
|
||||
SET(CMAKE_SYSTEM_NAME Linux)
|
||||
SET(CMAKE_SYSTEM_VERSION 1)
|
||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
|
||||
# toolchain location
|
||||
set(TOOLCHAIN_URL https://toolchains.bootlin.com/downloads/releases/toolchains/aarch64/tarballs/aarch64--glibc--stable-2020.08-1.tar.bz2)
|
||||
|
||||
set(TOOLCHAIN_TAR aarch64--glibc--stable-2020.08-1.tar.bz2)
|
||||
|
||||
set(TOOLCHAIN_DIR aarch64--glibc--stable-2020.08-1)
|
||||
# for autotools
|
||||
set(TOOLCHAIN_TARGET aarch64-linux-gnu)
|
||||
|
||||
set(TOOLCHAIN_NAME aarch64-buildroot-linux-gnu)
|
||||
set(TOOLCHAIN_PATH ${CMAKE_CURRENT_LIST_DIR}/toolchains/${TOOLCHAIN_DIR})
|
||||
set(TOOLCHAIN_PREFIX ${CMAKE_CURRENT_LIST_DIR}/toolchains/${TOOLCHAIN_DIR}/bin/${TOOLCHAIN_NAME})
|
||||
|
||||
if(NOT EXISTS ${TOOLCHAIN_PATH})
|
||||
execute_process(COMMAND mkdir -p ${CMAKE_CURRENT_LIST_DIR}/toolchains)
|
||||
execute_process(COMMAND wget --progress=dot:giga ${TOOLCHAIN_URL})
|
||||
execute_process(COMMAND tar -xf ${TOOLCHAIN_TAR} -C ${CMAKE_CURRENT_LIST_DIR}/toolchains)
|
||||
endif()
|
||||
|
||||
# specify the cross compiler
|
||||
SET(CMAKE_C_COMPILER ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_NAME}-gcc)
|
||||
SET(CMAKE_CXX_COMPILER ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_NAME}-g++)
|
||||
set(CMAKE_STRIP ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_NAME}-strip CACHE INTERNAL "")
|
||||
set(CMAKE_OBJCOPY ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_NAME}-objcopy CACHE INTERNAL "")
|
||||
SET(CMAKE_LD ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_NAME}-ld)
|
||||
set(CMAKE_AR ${TOOLCHAIN_PATH}/bin/${TOOLCHAIN_NAME}-ar CACHE FILEPATH "Archiver")
|
||||
|
||||
|
||||
set(CMAKE_SYSROOT ${TOOLCHAIN_PATH}/${TOOLCHAIN_NAME}/sysroot)
|
||||
|
||||
# search for programs in the build host directories
|
||||
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
# for libraries and headers in the target directories
|
||||
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
@@ -0,0 +1 @@
|
||||
1.2.2.1
|
||||
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// Copyright © 2021 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.
|
||||
//
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include "versions.h"
|
||||
|
||||
const char * DEP_EXAMPLES_HASH_FULL = "4013852115ba69f9bbeb740816f3a1435dc2c075";
|
||||
const char * DEP_EXAMPLES_HASH_SHORT = "40138521";
|
||||
const bool DEP_EXAMPLES_STATE_CHANGES = 0 ? true : false;
|
||||
const char * DEP_EXAMPLES_COMPONENT_TAG_HASH = "4013852115ba69f9bbeb740816f3a1435dc2c075";
|
||||
const char * DEP_EXAMPLES_COMPONENT_TAG_HASH_SHORT = "40138521";
|
||||
const char * DEP_EXAMPLES_COMPONENT_TAG = "dep_examples_v1.2.2.rc1";
|
||||
const char * DEP_EXAMPLES_COMPONENT_TAG_VERSION = "1.2.2.rc1";
|
||||
const bool DEP_EXAMPLES_COMMITS_SINCE_COMPONENT_TAG = 0 ? true : false;
|
||||
const char * DEP_EXAMPLES_COMPONENT_TAG_VERSION_TYPE = "";
|
||||
const char * DEP_EXAMPLES_COMPONENT_TAG_VERSION_FULL = "1.2.2.1";
|
||||
const unsigned int DEP_EXAMPLES_VERSION_MAJOR = 1;
|
||||
const unsigned int DEP_EXAMPLES_VERSION_MINOR = 2;
|
||||
const unsigned int DEP_EXAMPLES_VERSION_PATCH = 2;
|
||||
const char * DEP_EXAMPLES_VERSION_SUFFIX = "rc1";
|
||||
|
||||
#define PRINT_VERSION_BUF_SIZE 256
|
||||
const char * getVersionInfo() {
|
||||
static char versionStr[PRINT_VERSION_BUF_SIZE];
|
||||
snprintf(versionStr, sizeof(versionStr), "dep_examples : v%s:%s:%s",
|
||||
DEP_EXAMPLES_COMPONENT_TAG_VERSION,
|
||||
DEP_EXAMPLES_COMPONENT_TAG_VERSION_TYPE,
|
||||
DEP_EXAMPLES_HASH_SHORT);
|
||||
return versionStr;
|
||||
}
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// Copyright © 2021 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.
|
||||
//
|
||||
#pragma once
|
||||
#ifndef DEP_AUDIO_VERSIONS_H
|
||||
#define DEP_AUDIO_VERSIONS_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
extern const char * DEP_AUDIO_HASH_FULL ;
|
||||
extern const char * DEP_AUDIO_HASH_SHORT ;
|
||||
extern const bool DEP_AUDIO_STATE_CHANGES ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_HASH ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_HASH_SHORT ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_VERSION ;
|
||||
extern const bool DEP_AUDIO_COMMITS_SINCE_COMPONENT_TAG ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_VERSION_TYPE ;
|
||||
extern const char * DEP_AUDIO_COMPONENT_TAG_VERSION_FULL ;
|
||||
extern const unsigned int DEP_AUDIO_VERSION_MAJOR ;
|
||||
extern const unsigned int DEP_AUDIO_VERSION_MINOR ;
|
||||
extern const unsigned int DEP_AUDIO_VERSION_PATCH ;
|
||||
extern const char * DEP_AUDIO_VERSION_SUFFIX ;
|
||||
|
||||
const char * getVersionInfo();
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
//
|
||||
// Copyright © 2021 Audinate Pty Ltd ACN 120 828 006 (Audinate). All rights reserved.
|
||||
//
|
||||
Reference in New Issue
Block a user