summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/avtp_pipeline/avdecc_msg/openavb_avdecc_msg_client.c2
-rw-r--r--lib/avtp_pipeline/avtp/openavb_avtp.c158
-rw-r--r--lib/avtp_pipeline/avtp/openavb_avtp.h39
-rw-r--r--lib/avtp_pipeline/endpoint/openavb_endpoint.h224
-rwxr-xr-xlib/avtp_pipeline/include/openavb_map_pub.h23
-rwxr-xr-xlib/avtp_pipeline/map_aaf_audio/openavb_map_aaf_audio.c496
-rw-r--r--lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_file_talker.ini53
-rw-r--r--lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_listener.ini40
-rw-r--r--lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_listener_auto.ini40
-rw-r--r--lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_talker.ini67
-rw-r--r--lib/avtp_pipeline/platform/Linux/tl/openavb_tl_osal.c36
-rw-r--r--lib/avtp_pipeline/tl/openavb_talker.c34
-rwxr-xr-xlib/avtp_pipeline/tl/openavb_tl_pub.h5
13 files changed, 975 insertions, 242 deletions
diff --git a/lib/avtp_pipeline/avdecc_msg/openavb_avdecc_msg_client.c b/lib/avtp_pipeline/avdecc_msg/openavb_avdecc_msg_client.c
index 01c827f3..aea2bf9a 100644
--- a/lib/avtp_pipeline/avdecc_msg/openavb_avdecc_msg_client.c
+++ b/lib/avtp_pipeline/avdecc_msg/openavb_avdecc_msg_client.c
@@ -189,7 +189,7 @@ bool openavbAvdeccMsgClntTalkerStreamID(int avdeccMsgHandle, U8 sr_class, const
// Send a default stream_vlan_id value if none specified.
if (stream_vlan_id == 0) {
stream_vlan_id = 2; // SR Class default VLAN Id values per IEEE 802.1Q-2011 Table 9-2
- }
+ }
// Send the stream information to the server.
memset(&msgBuf, 0, OPENAVB_AVDECC_MSG_LEN);
diff --git a/lib/avtp_pipeline/avtp/openavb_avtp.c b/lib/avtp_pipeline/avtp/openavb_avtp.c
index 82932d23..cfea6adc 100644
--- a/lib/avtp_pipeline/avtp/openavb_avtp.c
+++ b/lib/avtp_pipeline/avtp/openavb_avtp.c
@@ -79,6 +79,17 @@ static openavbRC openAvtpSock(avtp_stream_t *pStream)
// Set the multicast address that we want to receive
openavbRawsockRxMulticast(pStream->rawsock, TRUE, pStream->dest_addr.ether_addr_octet);
}
+ else {
+ // Open any mirrored TX interfaces desired.
+ int i;
+ for (i = 0; pStream->ifname_mirror[i] && i < MAX_NUM_INTERFACE_MIRRORS; ++i) {
+ pStream->rawsock_mirror[i] = openavbRawsockOpen(pStream->ifname_mirror[i], FALSE, TRUE, ETHERTYPE_AVTP, pStream->frameLen, pStream->nbuffers);
+ if (pStream->rawsock_mirror[i] == NULL) {
+ AVB_RC_LOG_RET(AVB_RC(OPENAVB_AVTP_FAILURE | OPENAVB_RC_RAWSOCK_OPEN));
+ }
+ openavbSetRxSignalMode(pStream->rawsock_mirror[i], pStream->bRxSignalMode);
+ }
+ }
AVB_RC_RET(OPENAVB_AVTP_SUCCESS);
}
@@ -117,7 +128,7 @@ openavbRC openavbAvtpTxInit(
media_q_t *pMediaQ,
openavb_map_cb_t *pMapCB,
openavb_intf_cb_t *pIntfCB,
- char *ifname,
+ char *(ifname[]),
AVBStreamID_t *streamID,
U8 *destAddr,
U32 max_transit_usec,
@@ -127,6 +138,8 @@ openavbRC openavbAvtpTxInit(
U16 nbuffers,
void **pStream_out)
{
+ int i;
+
AVB_TRACE_ENTRY(AVB_TRACE_AVTP);
AVB_LOG_DEBUG("Initialize");
@@ -156,7 +169,14 @@ openavbRC openavbAvtpTxInit(
pStream->max_transit_usec = max_transit_usec;
// and save other stuff needed to (re)open the socket
- pStream->ifname = strdup(ifname);
+ if (!ifname[0]) {
+ free(pStream);
+ AVB_RC_LOG_TRACE_RET(AVB_RC(OPENAVB_AVTP_FAILURE | OPENAVB_RC_INVALID_ARGUMENT), AVB_TRACE_AVTP);
+ }
+ pStream->ifname = strdup(ifname[0]);
+ for (i = 0; ifname[i + 1] && ifname[i + 1][0] && i < MAX_NUM_INTERFACE_MIRRORS; ++i) {
+ pStream->ifname_mirror[i] = strdup(ifname[i + 1]);
+ }
pStream->nbuffers = nbuffers;
// Open a raw socket
@@ -170,37 +190,66 @@ openavbRC openavbAvtpTxInit(
hdr_info_t hdrInfo;
U8 srcAddr[ETH_ALEN];
- if (openavbRawsockGetAddr(pStream->rawsock, srcAddr)) {
- hdrInfo.shost = srcAddr;
- }
- else {
- openavbRawsockClose(pStream->rawsock);
- free(pStream);
- AVB_LOG_ERROR("Failed to get source MAC address");
- AVB_RC_TRACE_RET(OPENAVB_AVTP_FAILURE, AVB_TRACE_AVTP);
- }
+ for (i = -1; i < MAX_NUM_INTERFACE_MIRRORS; ++i) {
+ void *current_rawsock = (i < 0 ? pStream->rawsock : pStream->rawsock_mirror[i]);
+ if (!current_rawsock) break;
- hdrInfo.dhost = destAddr;
- if (vlanPCP != 0 || vlanID != 0) {
- hdrInfo.vlan = TRUE;
- hdrInfo.vlan_pcp = vlanPCP;
- hdrInfo.vlan_vid = vlanID;
- AVB_LOGF_DEBUG("VLAN pcp=%d vid=%d", hdrInfo.vlan_pcp, hdrInfo.vlan_vid);
- }
- else {
- hdrInfo.vlan = FALSE;
+ if (openavbRawsockGetAddr(current_rawsock, srcAddr)) {
+ hdrInfo.shost = srcAddr;
+ }
+ else if (i >= 0) {
+ AVB_LOGF_ERROR("Unable to get address for mirror interface %s - Interface disabled", pStream->ifname_mirror[i]);
+ for (; pStream->rawsock_mirror[i] && i < MAX_NUM_INTERFACE_MIRRORS; ++i) {
+ openavbRawsockClose(pStream->rawsock_mirror[i]);
+ pStream->rawsock_mirror[i] = NULL;
+ free(pStream->ifname_mirror[i]);
+ pStream->ifname_mirror[i] = NULL;
+ }
+ }
+ else {
+ if (pStream->rawsock) {
+ openavbRawsockClose(pStream->rawsock);
+ pStream->rawsock = NULL;
+ }
+ for (i = 0; pStream->rawsock_mirror[i] && i < MAX_NUM_INTERFACE_MIRRORS; ++i) {
+ openavbRawsockClose(pStream->rawsock_mirror[i]);
+ pStream->rawsock_mirror[i] = NULL;
+ }
+
+ free(pStream->ifname);
+ pStream->ifname = NULL;
+ for (i = 0; pStream->ifname_mirror[i] && i < MAX_NUM_INTERFACE_MIRRORS; ++i) {
+ free(pStream->ifname_mirror[i]);
+ pStream->ifname_mirror[i] = NULL;
+ }
+
+ free(pStream);
+ AVB_LOG_ERROR("Failed to get source MAC address");
+ AVB_RC_TRACE_RET(OPENAVB_AVTP_FAILURE, AVB_TRACE_AVTP);
+ }
+
+ hdrInfo.dhost = destAddr;
+ if (vlanPCP != 0 || vlanID != 0) {
+ hdrInfo.vlan = TRUE;
+ hdrInfo.vlan_pcp = vlanPCP;
+ hdrInfo.vlan_vid = vlanID;
+ AVB_LOGF_DEBUG("VLAN pcp=%d vid=%d", hdrInfo.vlan_pcp, hdrInfo.vlan_vid);
+ }
+ else {
+ hdrInfo.vlan = FALSE;
+ }
+ openavbRawsockTxSetHdr(current_rawsock, &hdrInfo);
+
+ // Set the fwmark - used to steer packets into the right traffic control queue
+ openavbRawsockTxSetMark(current_rawsock, fwmark);
}
- openavbRawsockTxSetHdr(pStream->rawsock, &hdrInfo);
// Remember the AVTP subtype and streamID
pStream->subtype = pStream->pMapCB->map_subtype_cb();
memcpy(pStream->streamIDnet, streamID->addr, ETH_ALEN);
- U16 *pStreamUID = (U16 *)((U8 *)(pStream->streamIDnet) + ETH_ALEN);
- *pStreamUID = htons(streamID->uniqueID);
-
- // Set the fwmark - used to steer packets into the right traffic control queue
- openavbRawsockTxSetMark(pStream->rawsock, fwmark);
+ U16 *pStreamUID = (U16 *)((U8 *)(pStream->streamIDnet) + ETH_ALEN);
+ *pStreamUID = htons(streamID->uniqueID);
*pStream_out = (void *)pStream;
AVB_RC_TRACE_RET(OPENAVB_AVTP_SUCCESS, AVB_TRACE_AVTP);
@@ -376,6 +425,30 @@ openavbRC openavbAvtpTx(void *pv, bool bSend, bool txBlockingInIntf)
// Increment the sequence number now that we are sure this is a good packet.
pStream->avtp_sequence_num++;
+
+ // Mirror the frame.
+ int i;
+ for (i = 0; pStream->rawsock_mirror[i] && i < MAX_NUM_INTERFACE_MIRRORS; ++i) {
+ // Get a buffer from the mirrored socket.
+ U8 *pMirrorBuf = (U8 *)openavbRawsockGetTxFrame(pStream->rawsock_mirror[i], TRUE, &frameLen);
+ if (pMirrorBuf) {
+ assert(frameLen >= pStream->frameLen);
+ // Fill in the Ethernet header
+ openavbRawsockTxFillHdr(pStream->rawsock_mirror[i], pMirrorBuf, &pStream->ethHdrLen);
+
+ // Make a copy of the original buffer.
+ memcpy(pMirrorBuf, pStream->pBuf, avtpFrameLen + pStream->ethHdrLen);
+
+ // Mark the frame "ready to send".
+ openavbRawsockTxFrameReady(pStream->rawsock_mirror[i], pMirrorBuf, avtpFrameLen + pStream->ethHdrLen, timeNsec);
+ // Send if requested
+ if (bSend)
+ openavbRawsockSend(pStream->rawsock_mirror[i]);
+ // Drop our reference to it
+ pMirrorBuf = NULL;
+ }
+ }
+
// Mark the frame "ready to send".
openavbRawsockTxFrameReady(pStream->rawsock, pStream->pBuf, avtpFrameLen + pStream->ethHdrLen, timeNsec);
// Send if requested
@@ -498,6 +571,11 @@ static void x_avtpRxFrame(avtp_stream_t *pStream, U8 *pFrame, U32 frameLen)
AVB_LOGF_INFO("AVTP sequence mismatch: expected: %3u,\tgot: %3u,\tlost %3d",
pStream->avtp_sequence_num, rxSeq, nLost);
pStream->nLost += nLost;
+
+ // Notify the map that frames were lost.
+ if (pStream->pMapCB->map_rx_lost_cb) {
+ pStream->pMapCB->map_rx_lost_cb(pStream->pMediaQ, nLost);
+ }
}
pStream->avtp_sequence_num = rxSeq + 1;
@@ -710,6 +788,8 @@ void openavbAvtpShutdownTalker(void *pv)
avtp_stream_t *pStream = (avtp_stream_t *)pv;
if (pStream) {
+ int i;
+
pStream->pIntfCB->intf_end_cb(pStream->pMediaQ);
pStream->pMapCB->map_end_cb(pStream->pMediaQ);
@@ -718,9 +798,17 @@ void openavbAvtpShutdownTalker(void *pv)
openavbRawsockClose(pStream->rawsock);
pStream->rawsock = NULL;
}
+ for (i = 0; pStream->rawsock_mirror[i] && i < MAX_NUM_INTERFACE_MIRRORS; ++i) {
+ openavbRawsockClose(pStream->rawsock_mirror[i]);
+ pStream->rawsock_mirror[i] = NULL;
+ }
- if (pStream->ifname)
- free(pStream->ifname);
+ free(pStream->ifname);
+ pStream->ifname = NULL;
+ for (i = 0; pStream->ifname_mirror[i] && i < MAX_NUM_INTERFACE_MIRRORS; ++i) {
+ free(pStream->ifname_mirror[i]);
+ pStream->ifname_mirror[i] = NULL;
+ }
// free the malloc'd stream info
free(pStream);
@@ -736,17 +824,27 @@ void openavbAvtpShutdownListener(void *pv)
avtp_stream_t *pStream = (avtp_stream_t *)pv;
if (pStream) {
+ int i;
+
// close the rawsock
if (pStream->rawsock) {
openavbRawsockClose(pStream->rawsock);
pStream->rawsock = NULL;
}
+ for (i = 0; pStream->rawsock_mirror[i] && i < MAX_NUM_INTERFACE_MIRRORS; ++i) {
+ openavbRawsockClose(pStream->rawsock_mirror[i]);
+ pStream->rawsock_mirror[i] = NULL;
+ }
pStream->pIntfCB->intf_end_cb(pStream->pMediaQ);
pStream->pMapCB->map_end_cb(pStream->pMediaQ);
- if (pStream->ifname)
- free(pStream->ifname);
+ free(pStream->ifname);
+ pStream->ifname = NULL;
+ for (i = 0; pStream->ifname_mirror[i] && i < MAX_NUM_INTERFACE_MIRRORS; ++i) {
+ free(pStream->ifname_mirror[i]);
+ pStream->ifname_mirror[i] = NULL;
+ }
// free the malloc'd stream info
free(pStream);
diff --git a/lib/avtp_pipeline/avtp/openavb_avtp.h b/lib/avtp_pipeline/avtp/openavb_avtp.h
index 13d9a364..71139271 100644
--- a/lib/avtp_pipeline/avtp/openavb_avtp.h
+++ b/lib/avtp_pipeline/avtp/openavb_avtp.h
@@ -2,16 +2,16 @@
Copyright (c) 2012-2015, Symphony Teleca Corporation, a Harman International Industries, Incorporated company
Copyright (c) 2016-2017, Harman International Industries, Incorporated
All rights reserved.
-
+
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
-
+
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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 LISTED "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
@@ -22,10 +22,10 @@ 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.
-
-Attributions: The inih library portion of the source code is licensed from
-Brush Technology and Ben Hoyt - Copyright (c) 2009, Brush Technology and Copyright (c) 2009, Ben Hoyt.
-Complete license and copyright information can be found at
+
+Attributions: The inih library portion of the source code is licensed from
+Brush Technology and Ben Hoyt - Copyright (c) 2009, Brush Technology and Copyright (c) 2009, Ben Hoyt.
+Complete license and copyright information can be found at
https://github.com/benhoyt/inih/commit/74d2ca064fb293bc60a77b0bd068075b293cf175.
*************************************************************************************************************/
@@ -41,6 +41,7 @@ https://github.com/benhoyt/inih/commit/74d2ca064fb293bc60a77b0bd068075b293cf175.
#include "openavb_platform.h"
#include "openavb_intf_pub.h"
#include "openavb_map_pub.h"
+#include "openavb_tl_pub.h"
#include "openavb_rawsock.h"
#include "openavb_timestamp.h"
@@ -71,7 +72,7 @@ typedef struct {
struct timespec lastTime;
#endif
} avtp_rx_info_t;
-
+
typedef struct {
U8 *data; // pointer to data
avtp_rx_info_t rx; // re-assembly info
@@ -86,21 +87,25 @@ typedef struct {
*
* The void* handle that is returned to the client
* really is a pointer to an avtp_stream_t.
- *
+ *
* TODO: This passed around as void * handle can be typed since the avtp_stream_t is
* now seen by the talker / listern module.
- *
+ *
*/
typedef struct
{
// TX socket?
bool tx;
- // Interface name
+ // Interface name for base interface
char* ifname;
+ /// Network interface name for interfaces to TX mirror the AVTP traffic to.
+ char* (ifname_mirror[MAX_NUM_INTERFACE_MIRRORS]);
// Number of rawsock buffers
U16 nbuffers;
- // The rawsock library handle. Used to send or receive frames.
+ // The rawsock library handle for the base interface.
void *rawsock;
+ // The rawsock library handle for interfaces to TX mirror the AVTP traffic to.
+ void *(rawsock_mirror[MAX_NUM_INTERFACE_MIRRORS]);
// The streamID - in network form
U8 streamIDnet[8];
// The destination address for stream
@@ -131,16 +136,16 @@ typedef struct
U8* pBuf;
// Ethernet header length
U32 ethHdrLen;
-
+
// Timestamp evaluation related
openavb_timestamp_eval_t tsEval;
- // Stat related
+ // Stat related
// RX frames lost
int nLost;
// Bytes sent or recieved
U64 bytes;
-
+
} avtp_stream_t;
@@ -150,7 +155,7 @@ typedef void (*avtp_listener_callback_fn)(void *pv, avtp_info_t *data);
openavbRC openavbAvtpTxInit(media_q_t *pMediaQ,
openavb_map_cb_t *pMapCB,
openavb_intf_cb_t *pIntfCB,
- char* ifname,
+ char* (ifname[]),
AVBStreamID_t *streamID,
U8* destAddr,
U32 max_transit_usec,
@@ -162,7 +167,7 @@ openavbRC openavbAvtpTxInit(media_q_t *pMediaQ,
openavbRC openavbAvtpTx(void *pv, bool bSend, bool txBlockingInIntf);
-openavbRC openavbAvtpRxInit(media_q_t *pMediaQ,
+openavbRC openavbAvtpRxInit(media_q_t *pMediaQ,
openavb_map_cb_t *pMapCB,
openavb_intf_cb_t *pIntfCB,
char* ifname,
diff --git a/lib/avtp_pipeline/endpoint/openavb_endpoint.h b/lib/avtp_pipeline/endpoint/openavb_endpoint.h
index a3f82078..152edb33 100644
--- a/lib/avtp_pipeline/endpoint/openavb_endpoint.h
+++ b/lib/avtp_pipeline/endpoint/openavb_endpoint.h
@@ -74,11 +74,11 @@ typedef enum {
// Client message parameters
//////////////////////////////
typedef struct {
- U8 destAddr[ETH_ALEN];
- U8 noMaapAllocate;
+ U8 destAddr[ETH_ALEN];
+ U8 noMaapAllocate;
AVBTSpec_t tSpec;
U8 srClass;
- U8 srRank;
+ U8 srRank;
U32 latency;
U32 txRate;
} openavbEndpointParams_TalkerRegister_t;
@@ -97,24 +97,24 @@ typedef struct {
// Server messages parameters
//////////////////////////////
typedef struct {
- char ifname[IFNAMSIZ + 10]; // Include space for the socket type prefix (e.g. "simple:eth0")
- U8 destAddr[ETH_ALEN];
- openavbSrpLsnrDeclSubtype_t lsnrDecl;
- U8 srClass;
- U32 classRate;
- U16 vlanID;
- U8 priority;
- U16 fwmark;
+ char ifname[IFNAMSIZ + 10]; // Include space for the socket type prefix (e.g. "simple:eth0")
+ U8 destAddr[ETH_ALEN];
+ openavbSrpLsnrDeclSubtype_t lsnrDecl;
+ U8 srClass;
+ U32 classRate;
+ U16 vlanID;
+ U8 priority;
+ U16 fwmark;
} openavbEndpointParams_TalkerCallback_t;
typedef struct {
- openavbSrpAttribType_t tlkrDecl;
- char ifname[IFNAMSIZ + 10]; // Include space for the socket type prefix (e.g. "simple:eth0")
- U8 destAddr[ETH_ALEN];
- AVBTSpec_t tSpec;
- U8 srClass;
- U32 latency;
- openavbSrpFailInfo_t failInfo;
+ openavbSrpAttribType_t tlkrDecl;
+ char ifname[IFNAMSIZ + 10]; // Include space for the socket type prefix (e.g. "simple:eth0")
+ U8 destAddr[ETH_ALEN];
+ AVBTSpec_t tSpec;
+ U8 srClass;
+ U32 latency;
+ openavbSrpFailInfo_t failInfo;
} openavbEndpointParams_ListenerCallback_t;
typedef struct {
@@ -124,35 +124,35 @@ typedef struct {
#define OPENAVB_ENDPOINT_MSG_LEN sizeof(openavbEndpointMessage_t)
typedef struct clientStream_t {
- struct clientStream_t *next; // next link list pointer
+ struct clientStream_t *next; // next link list pointer
- int clientHandle; // ID that links this info to client (talker or listener)
+ int clientHandle; // ID that links this info to client (talker or listener)
// Information provided by the client (talker or listener)
- AVBStreamID_t streamID; // stream identifier
- clientRole_t role; // is client a talker or listener?
+ AVBStreamID_t streamID; // stream identifier
+ clientRole_t role; // is client a talker or listener?
// Information provided by the client (talker)
- AVBTSpec_t tSpec; // traffic specification (bandwidth for reservation)
- SRClassIdx_t srClass; // AVB class
- U8 srRank; // AVB rank
- U32 latency; // internal latency
- U32 txRate; // frames per second
+ AVBTSpec_t tSpec; // traffic specification (bandwidth for reservation)
+ SRClassIdx_t srClass; // AVB class
+ U8 srRank; // AVB rank
+ U32 latency; // internal latency
+ U32 txRate; // frames per second
// Information provided by SRP
- U8 priority; // AVB priority to use for stream
- U16 vlanID; // VLAN ID to use for stream
- U32 classRate; // observation intervals per second
+ U8 priority; // AVB priority to use for stream
+ U16 vlanID; // VLAN ID to use for stream
+ U32 classRate; // observation intervals per second
// Information provided by MAAP
- void *hndMaap; // handle for MAAP address allocation
- U8 destAddr[ETH_ALEN]; // destination MAC address (from config or MAAP)
+ void *hndMaap; // handle for MAAP address allocation
+ U8 destAddr[ETH_ALEN]; // destination MAC address (from config or MAAP)
// Information provided by Shaper
- void *hndShaper; // handle for Shaping configuration
+ void *hndShaper; // handle for Shaping configuration
// Information provided by QMgr
- int fwmark; // mark to identify packets of this stream
+ int fwmark; // mark to identify packets of this stream
} clientStream_t;
int startPTP(void);
@@ -168,16 +168,18 @@ bool x_talkerDeregister(clientStream_t *ps);
bool x_listenerDetach(clientStream_t *ps);
-openavbRC strmRegCb(void *pv,
- openavbSrpAttribType_t tlkrDecl,
- U8 destAddr[],
- AVBTSpec_t *tSpec,
- SRClassIdx_t srClass,
- U32 accumLatency,
- openavbSrpFailInfo_t *failInfo);
+openavbRC strmRegCb(
+ void *pv,
+ openavbSrpAttribType_t tlkrDecl,
+ U8 destAddr[],
+ AVBTSpec_t *tSpec,
+ SRClassIdx_t srClass,
+ U32 accumLatency,
+ openavbSrpFailInfo_t *failInfo);
-openavbRC strmAttachCb(void* pv,
- openavbSrpLsnrDeclSubtype_t lsnrDecl);
+openavbRC strmAttachCb(
+ void* pv,
+ openavbSrpLsnrDeclSubtype_t lsnrDecl);
#include "openavb_endpoint_osal.h"
@@ -197,9 +199,9 @@ void openavbEptSrvrCloseClientConnection(int h);
// Immediately after establishing connection with the endpoint server,
// the endpoint client checks that the server's version is the same as its own.
-// The client sends a request to the cleint for its version.
+// The client sends a request to the client for its version.
// The server handles the request and sends its version to the requesting client.
-// The clinet compares the recevied version to its own.
+// The client compares the received version to its own.
bool openavbEptClntRequestVersionFromServer(int h);
bool openavbEptSrvrHndlVerRqstFromClient(int h);
void openavbEptSrvrSendServerVersionToClient(int h, U32 AVBVersion);
@@ -208,79 +210,87 @@ void openavbEptClntCheckVerMatchesSrvr(int h, U32 AVBVersion);
// Each talker registers its stream with SRP via Endpoint.
// Endpoint communication is from client to server
-bool openavbEptClntRegisterStream(int h,
- AVBStreamID_t *streamID,
- U8 destAddr[],
- U8 noMaapAllocation,
- AVBTSpec_t *tSpec,
- U8 srClass,
- U8 srRank,
- U32 latency,
- U32 txRate);
-bool openavbEptSrvrRegisterStream(int h,
- AVBStreamID_t *streamID,
- U8 destAddr[],
- U8 noMaapAllocation,
- AVBTSpec_t *tSpec,
- U8 srClass,
- U8 srRank,
- U32 latency,
- U32 txRate);
+bool openavbEptClntRegisterStream(
+ int h,
+ AVBStreamID_t *streamID,
+ U8 destAddr[],
+ U8 noMaapAllocation,
+ AVBTSpec_t *tSpec,
+ U8 srClass,
+ U8 srRank,
+ U32 latency,
+ U32 txRate);
+bool openavbEptSrvrRegisterStream(
+ int h,
+ AVBStreamID_t *streamID,
+ U8 destAddr[],
+ U8 noMaapAllocation,
+ AVBTSpec_t *tSpec,
+ U8 srClass,
+ U8 srRank,
+ U32 latency,
+ U32 txRate);
// Each lister attaches to the stream it wants to receive;
// specifically the listener indicates interest / declaration to SRP.
// Endpoint communication is from client to server.
-bool openavbEptClntAttachStream(int h,
- AVBStreamID_t *streamID,
- openavbSrpLsnrDeclSubtype_t ld);
-bool openavbEptSrvrAttachStream(int h,
- AVBStreamID_t *streamID,
- openavbSrpLsnrDeclSubtype_t ld);
+bool openavbEptClntAttachStream(
+ int h,
+ AVBStreamID_t *streamID,
+ openavbSrpLsnrDeclSubtype_t ld);
+bool openavbEptSrvrAttachStream(
+ int h,
+ AVBStreamID_t *streamID,
+ openavbSrpLsnrDeclSubtype_t ld);
// SRP notifies the talker when its stream has been established to taken down.
// Endpoint communication is from server to client.
-void openavbEptSrvrNotifyTlkrOfSrpCb(int h,
- AVBStreamID_t *streamID,
- char *ifname,
- U8 destAddr[],
- openavbSrpLsnrDeclSubtype_t lsnrDecl,
- U8 srClass,
- U32 classRate,
- U16 vlanID,
- U8 priority,
- U16 fwmark);
-void openavbEptClntNotifyTlkrOfSrpCb(int h,
- AVBStreamID_t *streamID,
- char *ifname,
- U8 destAddr[],
- openavbSrpLsnrDeclSubtype_t lsnrDecl,
- U8 srClass,
- U32 classRate,
- U16 vlanID,
- U8 priority,
- U16 fwmark);
+void openavbEptSrvrNotifyTlkrOfSrpCb(
+ int h,
+ AVBStreamID_t *streamID,
+ char *ifname,
+ U8 destAddr[],
+ openavbSrpLsnrDeclSubtype_t lsnrDecl,
+ U8 srClass,
+ U32 classRate,
+ U16 vlanID,
+ U8 priority,
+ U16 fwmark);
+void openavbEptClntNotifyTlkrOfSrpCb(
+ int h,
+ AVBStreamID_t *streamID,
+ char *ifname,
+ U8 destAddr[],
+ openavbSrpLsnrDeclSubtype_t lsnrDecl,
+ U8 srClass,
+ U32 classRate,
+ U16 vlanID,
+ U8 priority,
+ U16 fwmark);
// SRP notifies the listener when its stream is available, failed or gone away.
// Endpoint communication is from server to client.
-void openavbEptSrvrNotifyLstnrOfSrpCb(int h,
- AVBStreamID_t *streamID,
- char *ifname,
- U8 destAddr[],
- openavbSrpAttribType_t tlkrDecl,
- AVBTSpec_t *tSpec,
- U8 srClass,
- U32 latency,
- openavbSrpFailInfo_t *failInfo);
-void openavbEptClntNotifyLstnrOfSrpCb(int h,
- AVBStreamID_t *streamID,
- char *ifname,
- U8 destAddr[],
- openavbSrpAttribType_t tlkrDecl,
- AVBTSpec_t *tSpec,
- U8 srClass,
- U32 latency,
- openavbSrpFailInfo_t *failInfo);
+void openavbEptSrvrNotifyLstnrOfSrpCb(
+ int h,
+ AVBStreamID_t *streamID,
+ char *ifname,
+ U8 destAddr[],
+ openavbSrpAttribType_t tlkrDecl,
+ AVBTSpec_t *tSpec,
+ U8 srClass,
+ U32 latency,
+ openavbSrpFailInfo_t *failInfo);
+void openavbEptClntNotifyLstnrOfSrpCb(
+ int h,
+ AVBStreamID_t *streamID,
+ char *ifname,
+ U8 destAddr[],
+ openavbSrpAttribType_t tlkrDecl,
+ AVBTSpec_t *tSpec,
+ U8 srClass,
+ U32 latency,
+ openavbSrpFailInfo_t *failInfo);
// A talker can withdraw its stream registration at any time;
diff --git a/lib/avtp_pipeline/include/openavb_map_pub.h b/lib/avtp_pipeline/include/openavb_map_pub.h
index 02a58957..8e4abeea 100755
--- a/lib/avtp_pipeline/include/openavb_map_pub.h
+++ b/lib/avtp_pipeline/include/openavb_map_pub.h
@@ -2,16 +2,16 @@
Copyright (c) 2012-2015, Symphony Teleca Corporation, a Harman International Industries, Incorporated company
Copyright (c) 2016-2017, Harman International Industries, Incorporated
All rights reserved.
-
+
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
-
+
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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 LISTED "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
@@ -22,10 +22,10 @@ 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.
-
-Attributions: The inih library portion of the source code is licensed from
-Brush Technology and Ben Hoyt - Copyright (c) 2009, Brush Technology and Copyright (c) 2009, Ben Hoyt.
-Complete license and copyright information can be found at
+
+Attributions: The inih library portion of the source code is licensed from
+Brush Technology and Ben Hoyt - Copyright (c) 2009, Brush Technology and Copyright (c) 2009, Ben Hoyt.
+Complete license and copyright information can be found at
https://github.com/benhoyt/inih/commit/74d2ca064fb293bc60a77b0bd068075b293cf175.
*************************************************************************************************************/
@@ -136,6 +136,13 @@ typedef void (*openavb_map_rx_init_cb_t)(media_q_t *pMediaQ);
*/
typedef bool (*openavb_map_rx_cb_t)(media_q_t *pMediaQ, U8 *pData, U32 datalen);
+/** This callback occurs when running as a listener and packets are lost.
+ *
+ * \param pMediaQ A pointer to the media queue for this stream
+ * \param numLost The number of sequence numbers missing (i.e. number of lost packets)
+ */
+typedef bool (*openavb_map_rx_lost_cb_t)(media_q_t *pMediaQ, U8 numLost);
+
/** This callback will be called when the stream is closing.
*
* \param pMediaQ A pointer to the media queue for this stream
@@ -200,6 +207,8 @@ typedef struct {
openavb_map_rx_init_cb_t map_rx_init_cb;
/// Receive callback.
openavb_map_rx_cb_t map_rx_cb;
+ /// Receive lost callback.
+ openavb_map_rx_lost_cb_t map_rx_lost_cb;
/// Stream end callback.
openavb_map_end_cb_t map_end_cb;
/// General shutdown callback.
diff --git a/lib/avtp_pipeline/map_aaf_audio/openavb_map_aaf_audio.c b/lib/avtp_pipeline/map_aaf_audio/openavb_map_aaf_audio.c
index f9501750..e3a5906a 100755
--- a/lib/avtp_pipeline/map_aaf_audio/openavb_map_aaf_audio.c
+++ b/lib/avtp_pipeline/map_aaf_audio/openavb_map_aaf_audio.c
@@ -48,6 +48,25 @@ https://github.com/benhoyt/inih/commit/74d2ca064fb293bc60a77b0bd068075b293cf175.
#define AVB_LOG_COMPONENT "AAF Mapping"
#include "openavb_log_pub.h"
+
+typedef struct {
+ U8 * queueStorage; // Buffer used as a circular queue for storing samples
+ U8 * queueHead; // Head of the circular queue
+ U8 * queueTail; // Tail of the circular queue
+ U32 queueSize;
+} circular_queue_t;
+
+static bool AllocateCircularQueue(circular_queue_t *pQueue, U32 nQueueSize);
+static void FreeCircularQueue(circular_queue_t *pQueue);
+
+static bool CircularQueueIsValid(const circular_queue_t *pQueue);
+static U32 CircularQueueBytesQueued(const circular_queue_t *pQueue);
+
+static void PushBufferToCircularQueue(circular_queue_t *pQueue, const U8 *pData, U32 nDataSize);
+static void PullBufferFromCircularQueue(circular_queue_t *pQueue, U8 *pData, U32 nDataSize);
+static bool CompareBufferToCircularQueue(const circular_queue_t *pQueue, const U8 *pData, U32 nDataSize);
+
+
#define AVTP_SUBTYPE_AAF 2
// Header sizes (bytes)
@@ -114,6 +133,7 @@ typedef struct {
/////////////
// Config data
/////////////
+
// map_nv_item_count
U32 itemCount;
@@ -133,9 +153,17 @@ typedef struct {
// MCR clock recovery interval
U32 mcrRecoveryInterval;
+ // Time in microseconds to transmit a second redundant stream. 0 (default) if feature disabled.
+ // This is also referred to as Max Allowed Dropout Time (MADT)
+ U32 temporalRedundantOffsetUsec;
+
+ // How frequently to report statistics.
+ U32 report_seconds;
+
/////////////
// Variable data
/////////////
+
U32 maxTransitUsec; // In microseconds
aaf_nominal_sample_rate_t aaf_rate;
@@ -155,6 +183,19 @@ typedef struct {
bool mediaQItemSyncTS;
+ U32 temporalRedundantOffsetSamples;
+ U32 temporalRedundantOffsetPackets;
+
+ // Temporal Redundancy data queue
+ circular_queue_t temporalRedundantQueue;
+ U32 temporalRedundantQueueFrameSize;
+
+ // Temporal Redundancy Listener support and statistics
+ circular_queue_t trStatsEntryTypeQueue;
+ U32 trStatsTotalFrames, trStatsLostFrames, trStatsNeededAvailable, trStatsNeededNotAvailable;
+
+ U64 nextReportNS;
+
} pvt_data_t;
static void x_calculateSizes(media_q_t *pMediaQ)
@@ -172,33 +213,43 @@ static void x_calculateSizes(media_q_t *pMediaQ)
switch (pPubMapInfo->audioRate) {
case AVB_AUDIO_RATE_8KHZ:
pPvtData->aaf_rate = AAF_RATE_8K;
+ pPvtData->temporalRedundantOffsetSamples = (U32) ((U64) pPvtData->temporalRedundantOffsetUsec * 8000ull / 1000000ull);
break;
case AVB_AUDIO_RATE_16KHZ:
pPvtData->aaf_rate = AAF_RATE_16K;
+ pPvtData->temporalRedundantOffsetSamples = (U32) ((U64) pPvtData->temporalRedundantOffsetUsec * 16000ull / 1000000ull);
break;
case AVB_AUDIO_RATE_24KHZ:
pPvtData->aaf_rate = AAF_RATE_24K;
+ pPvtData->temporalRedundantOffsetSamples = (U32) ((U64) pPvtData->temporalRedundantOffsetUsec * 24000ull / 1000000ull);
break;
case AVB_AUDIO_RATE_32KHZ:
pPvtData->aaf_rate = AAF_RATE_32K;
+ pPvtData->temporalRedundantOffsetSamples = (U32) ((U64) pPvtData->temporalRedundantOffsetUsec * 32000ull / 1000000ull);
break;
case AVB_AUDIO_RATE_44_1KHZ:
pPvtData->aaf_rate = AAF_RATE_44K1;
+ pPvtData->temporalRedundantOffsetSamples = (U32) ((U64) pPvtData->temporalRedundantOffsetUsec * 44100ull / 1000000ull);
break;
case AVB_AUDIO_RATE_48KHZ:
pPvtData->aaf_rate = AAF_RATE_48K;
+ pPvtData->temporalRedundantOffsetSamples = (U32) ((U64) pPvtData->temporalRedundantOffsetUsec * 48000ull / 1000000ull);
break;
case AVB_AUDIO_RATE_88_2KHZ:
pPvtData->aaf_rate = AAF_RATE_88K2;
+ pPvtData->temporalRedundantOffsetSamples = (U32) ((U64) pPvtData->temporalRedundantOffsetUsec * 88200ull / 1000000ull);
break;
case AVB_AUDIO_RATE_96KHZ:
pPvtData->aaf_rate = AAF_RATE_96K;
+ pPvtData->temporalRedundantOffsetSamples = (U32) ((U64) pPvtData->temporalRedundantOffsetUsec * 96000ull / 1000000ull);
break;
case AVB_AUDIO_RATE_176_4KHZ:
pPvtData->aaf_rate = AAF_RATE_176K4;
+ pPvtData->temporalRedundantOffsetSamples = (U32) ((U64) pPvtData->temporalRedundantOffsetUsec * 176400ull / 1000000ull);
break;
case AVB_AUDIO_RATE_192KHZ:
pPvtData->aaf_rate = AAF_RATE_192K;
+ pPvtData->temporalRedundantOffsetSamples = (U32) ((U64) pPvtData->temporalRedundantOffsetUsec * 192000ull / 1000000ull);
break;
default:
AVB_LOG_ERROR("Invalid audio frequency configured");
@@ -305,6 +356,19 @@ static void x_calculateSizes(media_q_t *pMediaQ)
pPubMapInfo->framesPerPacket,
pPubMapInfo->packingFactor,
pPubMapInfo->itemSize);
+
+ // Temporal Redundancy adjustments
+ pPvtData->temporalRedundantQueueFrameSize = pPvtData->payloadSizeMaxListener;
+ pPvtData->payloadSizeMaxListener *= 2; // Double Listener max payload in case remote Talker using Temporal Redundancy
+ if (pPvtData->temporalRedundantOffsetUsec > 0) {
+ pPvtData->payloadSizeMaxTalker *= 2; // Double Talker max payload if using Temporal Redundancy
+
+ pPvtData->temporalRedundantOffsetPackets =
+ (pPvtData->temporalRedundantOffsetSamples / pPubMapInfo->framesPerPacket);
+
+ AVB_LOGF_INFO("temporal redundancy offset=%u microseconds, %u samples, %u packets",
+ pPvtData->temporalRedundantOffsetUsec, pPvtData->temporalRedundantOffsetSamples, pPvtData->temporalRedundantOffsetPackets);
+ }
}
AVB_TRACE_EXIT(AVB_TRACE_MAP);
@@ -359,6 +423,15 @@ void openavbMapAVTPAudioCfgCB(media_q_t *pMediaQ, const char *name, const char *
char *pEnd;
pPvtData->mcrRecoveryInterval = strtol(value, &pEnd, 10);
}
+ else if (strcmp(name, "map_nv_temporal_redundant_offset") == 0 ||
+ strcmp(name, "map_nv_max_allowed_dropout_time") == 0 ) {
+ char *pEnd;
+ pPvtData->temporalRedundantOffsetUsec = strtol(value, &pEnd, 10);
+ }
+ else if (strcmp(name, "map_nv_report_seconds") == 0) {
+ char *pEnd;
+ pPvtData->report_seconds = strtol(value, &pEnd, 10);
+ }
}
AVB_TRACE_EXIT(AVB_TRACE_MAP);
@@ -437,6 +510,27 @@ void openavbMapAVTPAudioGenInitCB(media_q_t *pMediaQ)
x_calculateSizes(pMediaQ);
openavbMediaQSetSize(pMediaQ, pPvtData->itemCount, pPubMapInfo->itemSize);
+ if (pPvtData->temporalRedundantOffsetUsec > 0 && pPvtData->temporalRedundantOffsetSamples > 0) {
+ if ((pPvtData->temporalRedundantOffsetSamples % pPubMapInfo->framesPerPacket) != 0) {
+ AVB_LOG_ERROR("Temporal Redundancy not supported when redundant data would be split between two packets");
+ return;
+ }
+
+ // Create a data queue big enough to meet our needs.
+ const U32 queueSize =
+ (pPvtData->temporalRedundantQueueFrameSize * (pPvtData->temporalRedundantOffsetPackets + 2));
+ FreeCircularQueue(&pPvtData->temporalRedundantQueue);
+ if (!AllocateCircularQueue(&pPvtData->temporalRedundantQueue, queueSize)) {
+ AVB_LOG_ERROR("Temporal Redundancy queue not allocated.");
+ return;
+ }
+
+ // Prefill the data queue with empty samples for the initial temporal redundancy processing.
+ // TODO: Do we need something besides zeros for AAF_FORMAT_FLOAT_32 or AAF_FORMAT_AES3_32?
+ PushBufferToCircularQueue(&pPvtData->temporalRedundantQueue, NULL,
+ (pPvtData->temporalRedundantQueueFrameSize * pPvtData->temporalRedundantOffsetPackets));
+ }
+
pPvtData->dataValid = TRUE;
}
AVB_TRACE_EXIT(AVB_TRACE_MAP);
@@ -447,12 +541,17 @@ void openavbMapAVTPAudioGenInitCB(media_q_t *pMediaQ)
void openavbMapAVTPAudioTxInitCB(media_q_t *pMediaQ)
{
AVB_TRACE_ENTRY(AVB_TRACE_MAP);
+
if (pMediaQ) {
pvt_data_t *pPvtData = pMediaQ->pPvtMapInfo;
- if (pPvtData) {
- pPvtData->isTalker = TRUE;
+ if (!pPvtData) {
+ AVB_LOG_ERROR("Private mapping module data not allocated.");
+ return;
}
+
+ pPvtData->isTalker = TRUE;
}
+
AVB_TRACE_EXIT(AVB_TRACE_MAP);
}
@@ -501,11 +600,32 @@ tx_cb_ret_t openavbMapAVTPAudioTxCB(media_q_t *pMediaQ, U8 *pData, U32 *dataLen)
return TX_CB_RET_PACKET_NOT_READY;
}
+ if (pPvtData->temporalRedundantOffsetUsec > 0) {
+ if ((*dataLen - TOTAL_HEADER_SIZE) < pPvtData->payloadSize * 2) {
+ AVB_LOG_ERROR("Not enough room in packet for temporal offset payload");
+ openavbMediaQTailUnlock(pMediaQ);
+ AVB_TRACE_EXIT(AVB_TRACE_MAP_DETAIL);
+ return TX_CB_RET_PACKET_NOT_READY;
+ }
+
+ if (!CircularQueueIsValid(&pPvtData->temporalRedundantQueue)) {
+ AVB_LOG_ERROR("No queue for temporal offset payload");
+ openavbMediaQTailUnlock(pMediaQ);
+ AVB_TRACE_EXIT(AVB_TRACE_MAP_DETAIL);
+ return TX_CB_RET_PACKET_NOT_READY;
+ }
+ }
+
U32 tmp32;
U8 *pHdrV0 = pData;
U32 *pHdr = (U32 *)(pData + AVTP_V0_HEADER_SIZE);
U8 *pPayload = pData + TOTAL_HEADER_SIZE;
+ if (pPvtData->temporalRedundantOffsetUsec > 0) {
+ // We want to write the supplied data to the redundant_audio_data_payload, rather than the primary_audio_data_payload
+ pPayload += bytesNeeded;
+ }
+
U32 bytesProcessed = 0;
while (bytesProcessed < bytesNeeded) {
pMediaQItem = openavbMediaQTailLock(pMediaQ, TRUE);
@@ -530,6 +650,11 @@ tx_cb_ret_t openavbMapAVTPAudioTxCB(media_q_t *pMediaQ, U8 *pData, U32 *dataLen)
// Add the max transit time.
openavbAvtpTimeAddUSec(pMediaQItem->pAvtpTime, pPvtData->maxTransitUsec);
+ // Add the max allowed dropout time, if used, so that the presentation timestamp includes that delay.
+ if (pPvtData->temporalRedundantOffsetUsec > 0) {
+ openavbAvtpTimeAddUSec(pMediaQItem->pAvtpTime, pPvtData->temporalRedundantOffsetUsec);
+ }
+
// Set timestamp valid flag
pHdrV0[HIDX_AVTP_HIDE7_TV1] |= 0x01;
@@ -592,6 +717,25 @@ tx_cb_ret_t openavbMapAVTPAudioTxCB(media_q_t *pMediaQ, U8 *pData, U32 *dataLen)
// Set out bound data length (entire packet length)
*dataLen = bytesNeeded + TOTAL_HEADER_SIZE;
+ if (pPvtData->temporalRedundantOffsetUsec > 0) {
+ // Push the data from the redundant_audio_data_payload to the circular queue, so we can use it in a later packet.
+ PushBufferToCircularQueue(&pPvtData->temporalRedundantQueue, pData + TOTAL_HEADER_SIZE + bytesNeeded, bytesNeeded);
+ if (bytesNeeded < pPvtData->temporalRedundantQueueFrameSize) {
+ // Pad to the end of the frame size.
+ PushBufferToCircularQueue(&pPvtData->temporalRedundantQueue, NULL, pPvtData->temporalRedundantQueueFrameSize - bytesNeeded);
+ }
+
+ // Pull the data from the circular queue to the primary_audio_data_payload.
+ PullBufferFromCircularQueue(&pPvtData->temporalRedundantQueue, pData + TOTAL_HEADER_SIZE, bytesNeeded);
+ if (bytesNeeded < pPvtData->temporalRedundantQueueFrameSize) {
+ // Go past padding at the end of the frame size.
+ PullBufferFromCircularQueue(&pPvtData->temporalRedundantQueue, NULL, pPvtData->temporalRedundantQueueFrameSize - bytesNeeded);
+ }
+
+ // Account for the larger packet size.
+ *dataLen += bytesNeeded;
+ }
+
AVB_TRACE_EXIT(AVB_TRACE_MAP_DETAIL);
return TX_CB_RET_PACKET_READY;
}
@@ -635,6 +779,19 @@ void openavbMapAVTPAudioRxInitCB(media_q_t *pMediaQ)
AVB_LOGF_WARNING("Wrong packing factor value set (%d) for sparse timestamping mode", pPvtData->packingFactor);
}
}
+
+ // Prepare to gather Temporal Redundancy statistics.
+ if (pPvtData->temporalRedundantOffsetUsec > 0) {
+ // Create a statistics tracking queue big enough to meet our needs.
+ FreeCircularQueue(&pPvtData->trStatsEntryTypeQueue);
+ AllocateCircularQueue(&pPvtData->trStatsEntryTypeQueue, pPvtData->temporalRedundantOffsetPackets + 10 /* Add some padding, just in case. */ );
+
+ // Record some initial failures, as the pre-filled redundant data is of type AAF_FORMAT_UNSPEC (0).
+ PushBufferToCircularQueue(&pPvtData->trStatsEntryTypeQueue, NULL, pPvtData->temporalRedundantOffsetPackets);
+
+ pPvtData->trStatsTotalFrames = pPvtData->trStatsLostFrames =
+ pPvtData->trStatsNeededAvailable = pPvtData->trStatsNeededNotAvailable = 0;
+ }
}
AVB_TRACE_EXIT(AVB_TRACE_MAP);
}
@@ -741,6 +898,12 @@ bool openavbMapAVTPAudioRxCB(media_q_t *pMediaQ, U8 *pData, U32 dataLen)
listenerSparseMode = FALSE;
}
+ if (pPvtData->temporalRedundantOffsetUsec > 0 &&
+ dataLen < TOTAL_HEADER_SIZE + (2 * payloadLen)) {
+ AVB_LOG_WARNING("Listener disabling temporal redundancy due to lack of data");
+ pPvtData->temporalRedundantOffsetUsec = 0;
+ }
+
if (dataValid) {
if (!pPvtData->dataValid) {
AVB_LOG_INFO("RX data valid, stream un-muted");
@@ -784,10 +947,10 @@ bool openavbMapAVTPAudioRxCB(media_q_t *pMediaQ, U8 *pData, U32 dataLen)
memcpy((uint8_t *)pMediaQItem->pPubData + pMediaQItem->dataLen, pPayload, pPvtData->payloadSize);
}
else {
- static U8 s_audioBuffer[1500];
U8 *pInData = pPayload;
- U8 *pInDataEnd = pPayload + payloadLen;
- U8 *pOutData = s_audioBuffer;
+ U8 *pInDataEnd = pInData + payloadLen;
+ U8 *pOutDataStart = (U8*) pMediaQItem->pPubData + pMediaQItem->dataLen;
+ U8 *pOutData = pOutDataStart;
int nInSampleLength = 6 - incoming_aaf_format; // Calculate the number of integer bytes per sample received
int nOutSampleLength = 6 - pPvtData->aaf_format; // Calculate the number of integer bytes per sample we want
int i;
@@ -811,15 +974,13 @@ bool openavbMapAVTPAudioRxCB(media_q_t *pMediaQ, U8 *pData, U32 dataLen)
pInData += (nInSampleLength - nOutSampleLength);
}
}
- if (pOutData - s_audioBuffer != pPvtData->payloadSize) {
- AVB_LOGF_ERROR("Output not expected size (%d instead of %d)", pOutData - s_audioBuffer, pPvtData->payloadSize);
+ if (pOutData - pOutDataStart != pPvtData->payloadSize) {
+ AVB_LOGF_ERROR("Output not expected size (%d instead of %d)", pOutData - pOutDataStart, pPvtData->payloadSize);
}
if (pPubMapInfo->intf_rx_translate_cb) {
- pPubMapInfo->intf_rx_translate_cb(pMediaQ, s_audioBuffer, pPvtData->payloadSize);
+ pPubMapInfo->intf_rx_translate_cb(pMediaQ, pOutDataStart, pPvtData->payloadSize);
}
-
- memcpy((uint8_t *)pMediaQItem->pPubData + pMediaQItem->dataLen, s_audioBuffer, pPvtData->payloadSize);
}
pMediaQItem->dataLen += pPvtData->payloadSize;
@@ -834,6 +995,53 @@ bool openavbMapAVTPAudioRxCB(media_q_t *pMediaQ, U8 *pData, U32 dataLen)
openavbMediaQHeadPush(pMediaQ);
}
+ if (pPvtData && pPvtData->temporalRedundantOffsetUsec > 0) {
+ // Save the pre-converted redundant data, and the format of the saved data.
+ U8 result = incoming_aaf_format; // Format of the redundant data.
+ PushBufferToCircularQueue(&pPvtData->trStatsEntryTypeQueue, &result, 1);
+ PushBufferToCircularQueue(&pPvtData->temporalRedundantQueue, pPayload + payloadLen, payloadLen);
+ if (payloadLen < pPvtData->temporalRedundantQueueFrameSize) {
+ // Pad to the end of the frame size.
+ PushBufferToCircularQueue(&pPvtData->temporalRedundantQueue, NULL, pPvtData->temporalRedundantQueueFrameSize - payloadLen);
+ }
+
+ // Discard the unnecessary redundant data previously saved.
+ // If debugging, verify that, if the redundant data was received earlier, it matches the received data.
+ U8 dataFormat = 0;
+ PullBufferFromCircularQueue(&pPvtData->trStatsEntryTypeQueue, &dataFormat, 1);
+#if (AVB_LOG_LEVEL >= AVB_LOG_LEVEL_DEBUG)
+ if (dataFormat != AAF_FORMAT_UNSPEC && !CompareBufferToCircularQueue(&pPvtData->temporalRedundantQueue, pPayload, payloadLen)) {
+ AVB_LOG_DEBUG("Redundant data does not match primary data.");
+ }
+#endif
+ PullBufferFromCircularQueue(&pPvtData->temporalRedundantQueue, NULL, pPvtData->temporalRedundantQueueFrameSize);
+
+ // Update the statistics.
+ pPvtData->trStatsTotalFrames++;
+
+ // Display the statistics.
+ if (pPvtData->report_seconds > 0) {
+ U64 nowNS;
+ CLOCK_GETTIME64(OPENAVB_TIMER_CLOCK, &nowNS);
+ if (nowNS > pPvtData->nextReportNS) {
+ AVB_LOGF_INFO("Temporal Redundancy Total Frames=%u, Lost Frames=%u, Available When Needed=%u, Not Available When Needed=%u",
+ pPvtData->trStatsTotalFrames, pPvtData->trStatsLostFrames,
+ pPvtData->trStatsNeededAvailable, pPvtData->trStatsNeededNotAvailable);
+ AVB_LOGF_DEBUG("Temporal Redundancy Data Queue Size=%u, Tracking Queue Size=%u",
+ CircularQueueBytesQueued(&pPvtData->temporalRedundantQueue),
+ CircularQueueBytesQueued(&pPvtData->trStatsEntryTypeQueue));
+
+ pPvtData->trStatsTotalFrames = pPvtData->trStatsLostFrames =
+ pPvtData->trStatsNeededAvailable = pPvtData->trStatsNeededNotAvailable = 0;
+
+ pPvtData->nextReportNS += (pPvtData->report_seconds * NANOSECONDS_PER_SECOND);
+ if (nowNS > pPvtData->nextReportNS) {
+ pPvtData->nextReportNS = nowNS + (pPvtData->report_seconds * NANOSECONDS_PER_SECOND);
+ }
+ }
+ }
+ }
+
AVB_TRACE_EXIT(AVB_TRACE_MAP_DETAIL);
return TRUE; // Normal exit
}
@@ -854,6 +1062,137 @@ bool openavbMapAVTPAudioRxCB(media_q_t *pMediaQ, U8 *pData, U32 dataLen)
return FALSE;
}
+// This callback occurs when running as a listener and data is not available.
+bool openavbMapAVTPAudioRxLostCB(media_q_t *pMediaQ, U8 numLost)
+{
+ AVB_TRACE_ENTRY(AVB_TRACE_MAP_DETAIL);
+
+ if (pMediaQ) {
+ pvt_data_t *pPvtData = pMediaQ->pPvtMapInfo;
+ if (pPvtData && pPvtData->temporalRedundantOffsetUsec > 0 && pPvtData->dataValid) {
+ while (numLost-- > 0) {
+ // Get item pointer in media queue
+ media_q_item_t *pMediaQItem = openavbMediaQHeadLock(pMediaQ);
+ if (pMediaQItem) {
+ media_q_pub_map_aaf_audio_info_t *pPubMapInfo = pMediaQ->pPubMapInfo;
+
+ // Update the statistics.
+ pPvtData->trStatsTotalFrames++;
+ pPvtData->trStatsLostFrames++;
+
+ // Clear the timestamp valid flag
+ openavbAvtpTimeSetTimestampValid(pMediaQItem->pAvtpTime, FALSE);
+
+ // Add the recovery data to pMediaQ.
+ U8 dataFormat = 0;
+ PullBufferFromCircularQueue(&pPvtData->trStatsEntryTypeQueue, &dataFormat, 1);
+ if (dataFormat == AAF_FORMAT_UNSPEC) {
+ pPvtData->trStatsNeededNotAvailable++;
+
+ // TODO: Do we need something besides zeros for AAF_FORMAT_FLOAT_32 or AAF_FORMAT_AES3_32?
+ PullBufferFromCircularQueue(&pPvtData->temporalRedundantQueue,
+ (U8*) pMediaQItem->pPubData + pMediaQItem->dataLen, pPvtData->payloadSize);
+ if (pPubMapInfo->intf_rx_translate_cb) {
+ pPubMapInfo->intf_rx_translate_cb(pMediaQ,
+ (U8*) pMediaQItem->pPubData + pMediaQItem->dataLen, pPvtData->payloadSize);
+ }
+ pMediaQItem->dataLen += pPvtData->payloadSize;
+ if (pPvtData->payloadSize < pPvtData->temporalRedundantQueueFrameSize) {
+ // Go past padding at the end of the frame size.
+ PullBufferFromCircularQueue(&pPvtData->temporalRedundantQueue, NULL,
+ pPvtData->temporalRedundantQueueFrameSize - pPvtData->payloadSize);
+ }
+ }
+ else {
+ pPvtData->trStatsNeededAvailable++;
+
+ // Convert the data, if needed.
+ if (dataFormat != pPvtData->aaf_format &&
+ dataFormat >= AAF_FORMAT_INT_32 && dataFormat <= AAF_FORMAT_INT_16 &&
+ pPvtData->aaf_format >= AAF_FORMAT_INT_32 && pPvtData->aaf_format <= AAF_FORMAT_INT_16) {
+
+ static U8 s_audioBuffer[1500];
+ int nInSampleLength = 6 - dataFormat; // Calculate the number of integer bytes per sample received
+ int nOutSampleLength = 6 - pPvtData->aaf_format; // Calculate the number of integer bytes per sample we want
+ int payloadLen = nInSampleLength * pPubMapInfo->audioChannels * pPubMapInfo->framesPerPacket;
+ U8 *pInData = s_audioBuffer;
+ U8 *pInDataEnd = pInData + payloadLen;
+ U8 *pOutDataStart = (U8*) pMediaQItem->pPubData + pMediaQItem->dataLen;
+ U8 *pOutData = pOutDataStart;
+ int i;
+
+ PullBufferFromCircularQueue(&pPvtData->temporalRedundantQueue,
+ s_audioBuffer, pPvtData->temporalRedundantQueueFrameSize);
+
+ if (nInSampleLength < nOutSampleLength) {
+ // We need to pad the data supplied.
+ while (pInData < pInDataEnd) {
+ for (i = 0; i < nInSampleLength; ++i) {
+ *pOutData++ = *pInData++;
+ }
+ for ( ; i < nOutSampleLength; ++i) {
+ *pOutData++ = 0; // Value specified in Clause 7.3.4.
+ }
+ }
+ }
+ else {
+ // We need to truncate the data supplied.
+ while (pInData < pInDataEnd) {
+ for (i = 0; i < nOutSampleLength; ++i) {
+ *pOutData++ = *pInData++;
+ }
+ pInData += (nInSampleLength - nOutSampleLength);
+ }
+ }
+ if (pOutData - pOutDataStart != pPvtData->payloadSize) {
+ AVB_LOGF_ERROR("Output not expected size (%d instead of %d)", pOutData - pOutDataStart, pPvtData->payloadSize);
+ }
+
+ if (pPubMapInfo->intf_rx_translate_cb) {
+ pPubMapInfo->intf_rx_translate_cb(pMediaQ, pOutDataStart, pPvtData->payloadSize);
+ }
+ }
+ else {
+ // Copy the data directly from the circular queue to the media queue.
+ PullBufferFromCircularQueue(&pPvtData->temporalRedundantQueue,
+ (U8*) pMediaQItem->pPubData + pMediaQItem->dataLen, pPvtData->payloadSize);
+ if (pPvtData->payloadSize < pPvtData->temporalRedundantQueueFrameSize) {
+ // Go past padding at the end of the frame size.
+ PullBufferFromCircularQueue(&pPvtData->temporalRedundantQueue, NULL,
+ pPvtData->temporalRedundantQueueFrameSize - pPvtData->payloadSize);
+ }
+
+ if (pPubMapInfo->intf_rx_translate_cb) {
+ pPubMapInfo->intf_rx_translate_cb(pMediaQ,
+ (U8*) pMediaQItem->pPubData + pMediaQItem->dataLen, pPvtData->payloadSize);
+ }
+ }
+
+ pMediaQItem->dataLen += pPvtData->payloadSize;
+ }
+
+ if (pMediaQItem->dataLen < pMediaQItem->itemSize) {
+ // More data can be written to the item
+ openavbMediaQHeadUnlock(pMediaQ);
+ }
+ else {
+ // The item is full push it.
+ openavbMediaQHeadPush(pMediaQ);
+ }
+
+ // Add some blank recovery data for this lost packet.
+ U8 result = AAF_FORMAT_UNSPEC; // AAF_FORMAT_UNSPEC (0) to indicate invalid recovery data.
+ PushBufferToCircularQueue(&pPvtData->trStatsEntryTypeQueue, &result, 1);
+ PushBufferToCircularQueue(&pPvtData->temporalRedundantQueue, NULL, pPvtData->temporalRedundantQueueFrameSize);
+ }
+ }
+ }
+ }
+
+ AVB_TRACE_EXIT(AVB_TRACE_MAP_DETAIL);
+ return FALSE;
+}
+
// This callback will be called when the mapping module needs to be closed.
// All cleanup should occur in this function.
void openavbMapAVTPAudioEndCB(media_q_t *pMediaQ)
@@ -880,6 +1219,15 @@ void openavbMapAVTPAudioEndCB(media_q_t *pMediaQ)
void openavbMapAVTPAudioGenEndCB(media_q_t *pMediaQ)
{
AVB_TRACE_ENTRY(AVB_TRACE_MAP);
+
+ if (pMediaQ) {
+ pvt_data_t *pPvtData = pMediaQ->pPvtMapInfo;
+ if (pPvtData) {
+ FreeCircularQueue(&pPvtData->temporalRedundantQueue);
+ FreeCircularQueue(&pPvtData->trStatsEntryTypeQueue);
+ }
+ }
+
AVB_TRACE_EXIT(AVB_TRACE_MAP);
}
@@ -910,6 +1258,7 @@ extern DLL_EXPORT bool openavbMapAVTPAudioInitialize(media_q_t *pMediaQ, openavb
pMapCB->map_tx_cb = openavbMapAVTPAudioTxCB;
pMapCB->map_rx_init_cb = openavbMapAVTPAudioRxInitCB;
pMapCB->map_rx_cb = openavbMapAVTPAudioRxCB;
+ pMapCB->map_rx_lost_cb = openavbMapAVTPAudioRxLostCB;
pMapCB->map_end_cb = openavbMapAVTPAudioEndCB;
pMapCB->map_gen_end_cb = openavbMapAVTPAudioGenEndCB;
@@ -920,12 +1269,139 @@ extern DLL_EXPORT bool openavbMapAVTPAudioInitialize(media_q_t *pMediaQ, openavb
pPvtData->sparseMode = TS_SPARSE_MODE_DISABLED;
pPvtData->mcrTimestampInterval = 144;
pPvtData->mcrRecoveryInterval = 512;
+ pPvtData->temporalRedundantOffsetUsec = 0;
pPvtData->aaf_event_field = AAF_STATIC_CHANNELS_LAYOUT;
pPvtData->intervalCounter = 0;
pPvtData->mediaQItemSyncTS = FALSE;
+ pPvtData->temporalRedundantOffsetSamples = 0;
openavbMediaQSetMaxLatency(pMediaQ, inMaxTransitUsec);
}
AVB_TRACE_EXIT(AVB_TRACE_MAP);
return TRUE;
}
+
+
+static bool AllocateCircularQueue(circular_queue_t *pQueue, U32 nQueueSize)
+{
+ // Create a queue big enough to meet our needs.
+ pQueue->queueSize = nQueueSize;
+ pQueue->queueStorage = malloc(pQueue->queueSize);
+ if (!pQueue->queueStorage) {
+ AVB_LOG_ERROR("Temporal Redundancy queue not allocated.");
+ return FALSE;
+ }
+ pQueue->queueHead = pQueue->queueTail = pQueue->queueStorage;
+ AVB_LOGF_DEBUG("Allocated Temporal Redundancy queue of size %u", pQueue->queueSize);
+ return TRUE;
+}
+
+static void FreeCircularQueue(circular_queue_t *pQueue)
+{
+ if (pQueue) {
+ free(pQueue->queueStorage);
+ pQueue->queueStorage = NULL;
+ pQueue->queueSize = 0;
+ pQueue->queueHead = pQueue->queueTail = NULL;
+ }
+}
+
+static bool CircularQueueIsValid(const circular_queue_t *pQueue)
+{
+ return (pQueue && pQueue->queueStorage && pQueue->queueSize);
+}
+
+static U32 CircularQueueBytesQueued(const circular_queue_t *pQueue)
+{
+ if (pQueue->queueTail > pQueue->queueHead) {
+ return (pQueue->queueHead + pQueue->queueSize - pQueue->queueTail);
+ }
+ return (pQueue->queueHead - pQueue->queueTail);
+}
+
+static void PushBufferToCircularQueue(circular_queue_t *pQueue, const U8 *pData, U32 nDataSize)
+{
+ U32 bytesToCopyPhase1, bytesToCopyPhase2;
+
+ // Copy the supplied data to the head of the circular queue, so we can use it in a later packet.
+ bytesToCopyPhase1 = pQueue->queueSize - (pQueue->queueHead - pQueue->queueStorage);
+ if (bytesToCopyPhase1 > nDataSize) {
+ bytesToCopyPhase1 = nDataSize;
+ }
+ if (pData) {
+ memcpy(pQueue->queueHead, pData, bytesToCopyPhase1);
+ }
+ else {
+ memset(pQueue->queueHead, 0, bytesToCopyPhase1);
+ }
+ pQueue->queueHead += bytesToCopyPhase1;
+ if (pQueue->queueHead >= pQueue->queueStorage + pQueue->queueSize) {
+ pQueue->queueHead = pQueue->queueStorage;
+
+ if (bytesToCopyPhase1 < nDataSize) {
+ // Didn't have enough bytes at the end of the storage buffer. Copy to the beginning of the buffer as well.
+ bytesToCopyPhase2 = nDataSize - bytesToCopyPhase1;
+ if (pData) {
+ memcpy(pQueue->queueHead, pData + bytesToCopyPhase1, bytesToCopyPhase2);
+ }
+ else {
+ memset(pQueue->queueHead, 0, bytesToCopyPhase2);
+ }
+ pQueue->queueHead += bytesToCopyPhase2;
+ }
+ }
+}
+
+static void PullBufferFromCircularQueue(circular_queue_t *pQueue, U8 *pData, U32 nDataSize)
+{
+ U32 bytesToCopyPhase1, bytesToCopyPhase2;
+
+ // Copy the data from the tail of the circular queue to the supplied data.
+ bytesToCopyPhase1 = pQueue->queueSize - (pQueue->queueTail - pQueue->queueStorage);
+ if (bytesToCopyPhase1 > nDataSize) {
+ bytesToCopyPhase1 = nDataSize;
+ }
+ if (pData) {
+ memcpy(pData, pQueue->queueTail, bytesToCopyPhase1);
+ }
+ pQueue->queueTail += bytesToCopyPhase1;
+ if (pQueue->queueTail >= pQueue->queueStorage + pQueue->queueSize) {
+ pQueue->queueTail = pQueue->queueStorage;
+
+ if (bytesToCopyPhase1 < nDataSize) {
+ // Didn't have enough bytes at the end of the storage buffer. Copy from the beginning of the buffer as well.
+ bytesToCopyPhase2 = nDataSize - bytesToCopyPhase1;
+ if (pData) {
+ memcpy(pData + bytesToCopyPhase1, pQueue->queueTail, bytesToCopyPhase2);
+ }
+ pQueue->queueTail += bytesToCopyPhase2;
+ }
+ }
+}
+
+// Return TRUE if the buffer matches the next data in the queue, FALSE otherwise.
+// The queue is not changed by this call.
+static bool CompareBufferToCircularQueue(const circular_queue_t *pQueue, const U8 *pData, U32 nDataSize)
+{
+ U32 bytesToComparePhase1, bytesToComparePhase2;
+
+ if (!pData) return FALSE;
+
+ // Compare the data from the tail of the circular queue to the supplied data.
+ bytesToComparePhase1 = pQueue->queueSize - (pQueue->queueTail - pQueue->queueStorage);
+ if (bytesToComparePhase1 > nDataSize) {
+ bytesToComparePhase1 = nDataSize;
+ }
+ if (memcmp(pData, pQueue->queueTail, bytesToComparePhase1) != 0) {
+ return FALSE;
+ }
+ if (bytesToComparePhase1 < nDataSize) {
+ // Didn't have enough bytes at the end of the storage buffer. Compare from the beginning of the buffer as well.
+ bytesToComparePhase2 = nDataSize - bytesToComparePhase1;
+ if (memcmp(pData + bytesToComparePhase2, pQueue->queueStorage, bytesToComparePhase2) != 0 ) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
diff --git a/lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_file_talker.ini b/lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_file_talker.ini
index 210ae966..40475e74 100644
--- a/lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_file_talker.ini
+++ b/lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_file_talker.ini
@@ -11,7 +11,7 @@ role = talker
# listener is launched.
#initial_state = stopped
-# stream_addr: Used on the listener and should be set to the
+# stream_addr: Used on the listener and should be set to the
# mac address of the talker.
#stream_addr = 00:25:64:48:ca:a8
@@ -21,7 +21,7 @@ stream_uid = 2
# dest_addr: destination multicast address for the stream.
#
-# If using SRP and MAAP, dynamic destination addresses are generated
+# If using SRP and MAAP, dynamic destination addresses are generated
# automatically by the talker and passed to the listner, and don't
# need to be configured.
#
@@ -35,27 +35,27 @@ stream_uid = 2
# needs to be set (to the same value) in both the talker and listener.
#
# The destination is a multicast address, not a real MAC address, so it
-# does not match the talker or listener's interface MAC. There are
+# does not match the talker or listener's interface MAC. There are
# several pools of those addresses for use by AVTP defined in 1722.
#
#dest_addr = 91:e0:f0:00:fe:00
-# max_interval_frames: The maximum number of packets that will be sent during
+# max_interval_frames: The maximum number of packets that will be sent during
# an observation interval. This is only used on the talker.
max_interval_frames = 1
-# sr_class: A talker only setting. Values are either A or B. If not set an internal
+# sr_class: A talker only setting. Values are either A or B. If not set an internal
# default is used.
sr_class = B
# sr_rank: A talker only setting. If not set an internal default is used.
#sr_rank = 1
-# max_transit_usec: Allows manually specifying a maximum transit time.
+# max_transit_usec: Allows manually specifying a maximum transit time.
# On the talker this value is added to the PTP walltime to create the AVTP Timestamp.
# On the listener this value is used to validate an expected valid timestamp range.
-# Note: For the listener the map_nv_item_count value must be set large enough to
-# allow buffering at least as many AVTP packets that can be transmitted during this
+# Note: For the listener the map_nv_item_count value must be set large enough to
+# allow buffering at least as many AVTP packets that can be transmitted during this
# max transit time.
max_transit_usec = 50000
@@ -63,10 +63,10 @@ max_transit_usec = 50000
# only on the talker.
#internal_latency = 0
-# max_stale: The number of microseconds beyond the presentation time that media queue items will be purged
+# max_stale: The number of microseconds beyond the presentation time that media queue items will be purged
# because they are too old (past the presentation time). This is only used on listener end stations.
-# Note: needing to purge old media queue items is often a sign of some other problem. For example: a delay at
-# stream startup before incoming packets are ready to be processed by the media sink. If this deficit
+# Note: needing to purge old media queue items is often a sign of some other problem. For example: a delay at
+# stream startup before incoming packets are ready to be processed by the media sink. If this deficit
# in processing or purging the old (stale) packets is not handled, syncing multiple listeners will be problematic.
#max_stale = 1000
@@ -74,19 +74,26 @@ max_transit_usec = 50000
# This is only used by the talker. If not set internal defaults are used.
#raw_tx_buffers = 1000
-# report_seconds: How often to output stats. Defaults to 10 seconds. 0 turns off the stats.
+# report_seconds: How often to output stats. Defaults to 10 seconds. 0 turns off the stats.
#report_seconds = 0
# Ethernet Interface Name. Only needed on some platforms when stack is built with no endpoint functionality
# ifname = eth0
+# Ethernet Interface Name for Mirrors. Comma-delimited list of up to four additional interfaces that AVTP traffic will
+# be transmitted to (along with the "ifname" interface). Endpoint functionality (such as stream reservations)
+# will be limited to the "ifname" interface, which must be specified if this feature is enabled.
+# No receiving (listening) will be done for AVTP packets on the mirrored interfaces.
+# The default is for no interfaces to be mirrored.
+#ifmirrorname = eth1
+
# vlan_id: VLAN Identifier (1-4094). Used in "no endpoint" builds. Defaults to 2.
# vlan_id = 2
#####################################################################
# Mapping module configuration
#####################################################################
-# map_lib: The name of the library file (commonly a .so file) that
+# map_lib: The name of the library file (commonly a .so file) that
# implements the Initialize function. Comment out the map_lib name
# and link in the .c file to the openavb_tl executable to embed the mapper
# directly into the executable unit. There is no need to change anything
@@ -111,10 +118,28 @@ map_nv_packing_factor = 32
# Default value used (0) when commented.
map_nv_sparse_mode = 0
+# map_nv_max_allowed_dropout_time: Specify a non-zero value to enable
+# support for the Temporally Redundant Audio Format.
+# The time value specified is the number of microseconds between when
+# the redundant and primary audio data payload are sent, so larger values
+# will increase the delay for when the Listener will play the audio.
+# In order for the redundancy to work correctly, both the Talker
+# and Listener need to specify the same map_nv_max_allowed_dropout_time
+# value, so the Listener will know which redundant information to use.
+# A value of 0 (the default) causes this feature to be disabled.
+# The Listener will automatically adjust its playback time to include
+# the additional max allowed dropout time specified for the Talker,
+# even if this feature is disabled for the Listener.
+#map_nv_max_allowed_dropout_time = 10000
+
+# map_nv_report_seconds: How often the mapping module should output stats.
+# 0 turns off the stats, which is also the default.
+#map_nv_report_seconds = 1
+
#####################################################################
# Interface module configuration
#####################################################################
-# intf_lib: The name of the library file (commonly a .so file) that
+# intf_lib: The name of the library file (commonly a .so file) that
# implements the Initialize function. Comment out the intf_lib name
# and link in the .c file to the openavb_tl executable to embed the interface
# directly into the executable unit. There is no need to change anything
diff --git a/lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_listener.ini b/lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_listener.ini
index c7c799de..c7a2a98a 100644
--- a/lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_listener.ini
+++ b/lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_listener.ini
@@ -15,7 +15,7 @@ role = listener
# listener is launched.
#initial_state = stopped
-# stream_addr: Used on the listener and should be set to the
+# stream_addr: Used on the listener and should be set to the
# mac address of the talker.
stream_addr = 84:7e:40:2b:63:f4
@@ -27,25 +27,25 @@ stream_uid = 2
# be set in the talker. If SRP is not being used the destination address
# needs to be set in both side the talker and listener.
# The destination is a multicast address, not a real MAC address, so it
-# does not match the talker or listener's interface MAC. There are
+# does not match the talker or listener's interface MAC. There are
# several pools of those addresses for use by AVTP defined in 1722.
# At this time they need to be locally administered and must be in the range
# of 91:E0:F0:00:FE:00 - 91:E0:F0:00:FE:FF.
# Typically :00 for the first stream, :01 for the second, etc.
#dest_addr = 91:e0:f0:00:fe:00
-# max_transit_usec: Allows manually specifying a maximum transit time.
+# max_transit_usec: Allows manually specifying a maximum transit time.
# On the talker this value is added to the PTP walltime to create the AVTP Timestamp.
# On the listener this value is used to validate an expected valid timestamp range.
-# Note: For the listener the map_nv_item_count value must be set large enough to
-# allow buffering at least as many AVTP packets that can be transmitted during this
+# Note: For the listener the map_nv_item_count value must be set large enough to
+# allow buffering at least as many AVTP packets that can be transmitted during this
# max transit time.
max_transit_usec = 50000
-# max_stale: The number of microseconds beyond the presentation time that media queue items will be purged
+# max_stale: The number of microseconds beyond the presentation time that media queue items will be purged
# because they are too old (past the presentation time). This is only used on listener end stations.
-# Note: needing to purge old media queue items is often a sign of some other problem. For example: a delay at
-# stream startup before incoming packets are ready to be processed by the media sink. If this deficit
+# Note: needing to purge old media queue items is often a sign of some other problem. For example: a delay at
+# stream startup before incoming packets are ready to be processed by the media sink. If this deficit
# in processing or purging the old (stale) packets is not handled, syncing multiple listeners will be problematic.
#max_stale = 1000
@@ -53,7 +53,7 @@ max_transit_usec = 50000
# This is only used by the listener. If not set internal defaults are used.
#raw_rx_buffers = 100
-# report_seconds: How often to output stats. Defaults to 10 seconds. 0 turns off the stats.
+# report_seconds: How often to output stats. Defaults to 10 seconds. 0 turns off the stats.
#report_seconds = 0
# Ethernet Interface Name. Only needed on some platforms when stack is built with no endpoint functionality
@@ -66,7 +66,7 @@ sampling_rates = 44100,48000,96000
#####################################################################
# Mapping module configuration
#####################################################################
-# map_lib: The name of the library file (commonly a .so file) that
+# map_lib: The name of the library file (commonly a .so file) that
# implements the Initialize function. Comment out the map_lib name
# and link in the .c file to the openavb_tl executable to embed the mapper
# directly into the executable unit. There is no need to change anything
@@ -94,10 +94,28 @@ map_nv_packing_factor = 32
# time should be valid in every 8th packet.
map_nv_sparse_mode = 0
+# map_nv_max_allowed_dropout_time: Specify a non-zero value to enable
+# support for the Temporally Redundant Audio Format.
+# The time value specified is the number of microseconds between when
+# the redundant and primary audio data payload are sent, so larger values
+# will increase the delay for when the Listener will play the audio.
+# In order for the redundancy to work correctly, both the Talker
+# and Listener need to specify the same map_nv_max_allowed_dropout_time
+# value, so the Listener will know which redundant information to use.
+# A value of 0 (the default) causes this feature to be disabled.
+# The Listener will automatically adjust its playback time to include
+# the additional max allowed dropout time specified for the Talker,
+# even if this feature is disabled for the Listener.
+#map_nv_max_allowed_dropout_time = 10000
+
+# map_nv_report_seconds: How often the mapping module should output stats.
+# 0 turns off the stats, which is also the default.
+#map_nv_report_seconds = 1
+
#####################################################################
# Interface module configuration
#####################################################################
-# intf_lib: The name of the library file (commonly a .so file) that
+# intf_lib: The name of the library file (commonly a .so file) that
# implements the Initialize function. Comment out the intf_lib name
# and link in the .c file to the openavb_tl executable to embed the interface
# directly into the executable unit. There is no need to change anything
diff --git a/lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_listener_auto.ini b/lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_listener_auto.ini
index d3d064b1..3578d7cd 100644
--- a/lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_listener_auto.ini
+++ b/lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_listener_auto.ini
@@ -15,7 +15,7 @@ role = listener
# listener is launched.
#initial_state = stopped
-# stream_addr: Used on the listener and should be set to the
+# stream_addr: Used on the listener and should be set to the
# mac address of the talker.
stream_addr = ba:bc:1a:ba:bc:1a
@@ -27,25 +27,25 @@ stream_uid = 2
# be set in the talker. If SRP is not being used the destination address
# needs to be set in both side the talker and listener.
# The destination is a multicast address, not a real MAC address, so it
-# does not match the talker or listener's interface MAC. There are
+# does not match the talker or listener's interface MAC. There are
# several pools of those addresses for use by AVTP defined in 1722.
# At this time they need to be locally administered and must be in the range
# of 91:E0:F0:00:FE:00 - 91:E0:F0:00:FE:FF.
# Typically :00 for the first stream, :01 for the second, etc.
dest_addr = 91:e0:f0:00:fe:01
-# max_transit_usec: Allows manually specifying a maximum transit time.
+# max_transit_usec: Allows manually specifying a maximum transit time.
# On the talker this value is added to the PTP walltime to create the AVTP Timestamp.
# On the listener this value is used to validate an expected valid timestamp range.
-# Note: For the listener the map_nv_item_count value must be set large enough to
-# allow buffering at least as many AVTP packets that can be transmitted during this
+# Note: For the listener the map_nv_item_count value must be set large enough to
+# allow buffering at least as many AVTP packets that can be transmitted during this
# max transit time.
max_transit_usec = 2000
-# max_stale: The number of microseconds beyond the presentation time that media queue items will be purged
+# max_stale: The number of microseconds beyond the presentation time that media queue items will be purged
# because they are too old (past the presentation time). This is only used on listener end stations.
-# Note: needing to purge old media queue items is often a sign of some other problem. For example: a delay at
-# stream startup before incoming packets are ready to be processed by the media sink. If this deficit
+# Note: needing to purge old media queue items is often a sign of some other problem. For example: a delay at
+# stream startup before incoming packets are ready to be processed by the media sink. If this deficit
# in processing or purging the old (stale) packets is not handled, syncing multiple listeners will be problematic.
#max_stale = 1000
@@ -53,7 +53,7 @@ max_transit_usec = 2000
# This is only used by the listener. If not set internal defaults are used.
#raw_rx_buffers = 100
-# report_seconds: How often to output stats. Defaults to 10 seconds. 0 turns off the stats.
+# report_seconds: How often to output stats. Defaults to 10 seconds. 0 turns off the stats.
#report_seconds = 0
# Ethernet Interface Name. Only needed on some platforms when stack is built with no endpoint functionality
@@ -62,7 +62,7 @@ max_transit_usec = 2000
#####################################################################
# Mapping module configuration
#####################################################################
-# map_lib: The name of the library file (commonly a .so file) that
+# map_lib: The name of the library file (commonly a .so file) that
# implements the Initialize function. Comment out the map_lib name
# and link in the .c file to the openavb_tl executable to embed the mapper
# directly into the executable unit. There is no need to change anything
@@ -90,10 +90,28 @@ map_nv_packing_factor = 32
# time should be valid in every 8th packet.
map_nv_sparse_mode = 0
+# map_nv_max_allowed_dropout_time: Specify a non-zero value to enable
+# support for the Temporally Redundant Audio Format.
+# The time value specified is the number of microseconds between when
+# the redundant and primary audio data payload are sent, so larger values
+# will increase the delay for when the Listener will play the audio.
+# In order for the redundancy to work correctly, both the Talker
+# and Listener need to specify the same map_nv_max_allowed_dropout_time
+# value, so the Listener will know which redundant information to use.
+# A value of 0 (the default) causes this feature to be disabled.
+# The Listener will automatically adjust its playback time to include
+# the additional max allowed dropout time specified for the Talker,
+# even if this feature is disabled for the Listener.
+#map_nv_max_allowed_dropout_time = 10000
+
+# map_nv_report_seconds: How often the mapping module should output stats.
+# 0 turns off the stats, which is also the default.
+#map_nv_report_seconds = 1
+
#####################################################################
# Interface module configuration
#####################################################################
-# intf_lib: The name of the library file (commonly a .so file) that
+# intf_lib: The name of the library file (commonly a .so file) that
# implements the Initialize function. Comment out the intf_lib name
# and link in the .c file to the openavb_tl executable to embed the interface
# directly into the executable unit. There is no need to change anything
diff --git a/lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_talker.ini b/lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_talker.ini
index a6a85071..970dd4db 100644
--- a/lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_talker.ini
+++ b/lib/avtp_pipeline/platform/Linux/intf_alsa/aaf_talker.ini
@@ -11,7 +11,7 @@ role = talker
# listener is launched.
#initial_state = stopped
-# stream_addr: Used on the listener and should be set to the
+# stream_addr: Used on the listener and should be set to the
# mac address of the talker.
#stream_addr = 00:25:64:48:ca:a8
@@ -21,7 +21,7 @@ stream_uid = 2
# dest_addr: destination multicast address for the stream.
#
-# If using SRP and MAAP, dynamic destination addresses are generated
+# If using SRP and MAAP, dynamic destination addresses are generated
# automatically by the talker and passed to the listner, and don't
# need to be configured.
#
@@ -35,41 +35,41 @@ stream_uid = 2
# needs to be set (to the same value) in both the talker and listener.
#
# The destination is a multicast address, not a real MAC address, so it
-# does not match the talker or listener's interface MAC. There are
+# does not match the talker or listener's interface MAC. There are
# several pools of those addresses for use by AVTP defined in 1722.
#
#dest_addr = 91:e0:f0:00:fe:00
-# max_interval_frames: The maximum number of packets that will be sent during
+# max_interval_frames: The maximum number of packets that will be sent during
# an observation interval. This is only used on the talker.
max_interval_frames = 1
-# sr_class: A talker only setting. Values are either A or B. If not set an internal
+# sr_class: A talker only setting. Values are either A or B. If not set an internal
# default is used.
sr_class = B
# sr_rank: A talker only setting. If not set an internal default is used.
#sr_rank = 1
-# max_transit_usec: Allows manually specifying a maximum transit time.
+# max_transit_usec: Allows manually specifying a maximum transit time.
# On the talker this value is added to the PTP walltime to create the AVTP Timestamp.
# On the listener this value is used to validate an expected valid timestamp range.
-# Note: For the listener the map_nv_item_count value must be set large enough to
-# allow buffering at least as many AVTP packets that can be transmitted during this
+# Note: For the listener the map_nv_item_count value must be set large enough to
+# allow buffering at least as many AVTP packets that can be transmitted during this
# max transit time.
max_transit_usec = 50000
# max_transmit_deficit_usec: Allows setting the maximum packet transmit rate deficit that will
# be recovered when a talker falls behind. This is only used on a talker side. When a talker
-# can not keep up with the specified transmit rate it builds up a deficit and will attempt to
-# make up for this deficit by sending more packets. There is normally some variability in the
+# can not keep up with the specified transmit rate it builds up a deficit and will attempt to
+# make up for this deficit by sending more packets. There is normally some variability in the
# transmit rate because of other demands on the system so this is expected. However, without this
-# bounding value the deficit could grew too large in cases such where more streams are started
-# than the system can support and when the number of streams is reduced the remaining streams
+# bounding value the deficit could grew too large in cases such where more streams are started
+# than the system can support and when the number of streams is reduced the remaining streams
# will attempt to recover this deficit by sending packets at a higher rate. This can cause a problem
-# at the listener side and significantly delay the recovery time before media playback will return
-# to normal. Typically this value can be set to the expected buffer size (in usec) that listeners are
-# expected to be buffering. For low latency solutions this is normally a small value. For non-live
+# at the listener side and significantly delay the recovery time before media playback will return
+# to normal. Typically this value can be set to the expected buffer size (in usec) that listeners are
+# expected to be buffering. For low latency solutions this is normally a small value. For non-live
# media playback such as video playback the listener side buffers can often be large enough to held many
# seconds of data.
max_transmit_deficit_usec = 50000
@@ -78,10 +78,10 @@ max_transmit_deficit_usec = 50000
# only on the talker.
#internal_latency = 0
-# max_stale: The number of microseconds beyond the presentation time that media queue items will be purged
+# max_stale: The number of microseconds beyond the presentation time that media queue items will be purged
# because they are too old (past the presentation time). This is only used on listener end stations.
-# Note: needing to purge old media queue items is often a sign of some other problem. For example: a delay at
-# stream startup before incoming packets are ready to be processed by the media sink. If this deficit
+# Note: needing to purge old media queue items is often a sign of some other problem. For example: a delay at
+# stream startup before incoming packets are ready to be processed by the media sink. If this deficit
# in processing or purging the old (stale) packets is not handled, syncing multiple listeners will be problematic.
#max_stale = 1000
@@ -89,12 +89,19 @@ max_transmit_deficit_usec = 50000
# This is only used by the talker. If not set internal defaults are used.
#raw_tx_buffers = 100
-# report_seconds: How often to output stats. Defaults to 10 seconds. 0 turns off the stats.
+# report_seconds: How often to output stats. Defaults to 10 seconds. 0 turns off the stats.
#report_seconds = 0
# Ethernet Interface Name. Only needed on some platforms when stack is built with no endpoint functionality
# ifname = eth0
+# Ethernet Interface Name for Mirrors. Comma-delimited list of up to four additional interfaces that AVTP traffic will
+# be transmitted to (along with the "ifname" interface). Endpoint functionality (such as stream reservations)
+# will be limited to the "ifname" interface, which must be specified if this feature is enabled.
+# No receiving (listening) will be done for AVTP packets on the mirrored interfaces.
+# The default is for no interfaces to be mirrored.
+#ifmirrorname = eth1
+
# vlan_id: VLAN Identifier (1-4094). Used in "no endpoint" builds. Defaults to 2.
# vlan_id = 2
@@ -105,7 +112,7 @@ sampling_rates = 44100,48000,96000
#####################################################################
# Mapping module configuration
#####################################################################
-# map_lib: The name of the library file (commonly a .so file) that
+# map_lib: The name of the library file (commonly a .so file) that
# implements the Initialize function. Comment out the map_lib name
# and link in the .c file to the openavb_tl executable to embed the mapper
# directly into the executable unit. There is no need to change anything
@@ -133,10 +140,28 @@ map_nv_packing_factor = 32
# Default value used (0) when commented.
map_nv_sparse_mode = 0
+# map_nv_max_allowed_dropout_time: Specify a non-zero value to enable
+# support for the Temporally Redundant Audio Format.
+# The time value specified is the number of microseconds between when
+# the redundant and primary audio data payload are sent, so larger values
+# will increase the delay for when the Listener will play the audio.
+# In order for the redundancy to work correctly, both the Talker
+# and Listener need to specify the same map_nv_max_allowed_dropout_time
+# value, so the Listener will know which redundant information to use.
+# A value of 0 (the default) causes this feature to be disabled.
+# The Listener will automatically adjust its playback time to include
+# the additional max allowed dropout time specified for the Talker,
+# even if this feature is disabled for the Listener.
+#map_nv_max_allowed_dropout_time = 10000
+
+# map_nv_report_seconds: How often the mapping module should output stats.
+# 0 turns off the stats, which is also the default.
+#map_nv_report_seconds = 1
+
#####################################################################
# Interface module configuration
#####################################################################
-# intf_lib: The name of the library file (commonly a .so file) that
+# intf_lib: The name of the library file (commonly a .so file) that
# implements the Initialize function. Comment out the intf_lib name
# and link in the .c file to the openavb_tl executable to embed the interface
# directly into the executable unit. There is no need to change anything
diff --git a/lib/avtp_pipeline/platform/Linux/tl/openavb_tl_osal.c b/lib/avtp_pipeline/platform/Linux/tl/openavb_tl_osal.c
index 8f3cf82b..87862d7f 100644
--- a/lib/avtp_pipeline/platform/Linux/tl/openavb_tl_osal.c
+++ b/lib/avtp_pipeline/platform/Linux/tl/openavb_tl_osal.c
@@ -320,6 +320,42 @@ static int openavbTLCfgCallback(void *user, const char *tlSection, const char *n
strncpy(pCfg->ifname, value, sizeof(pCfg->ifname) - 1);
valOK = TRUE;
}
+ else if (MATCH(name, "ifmirrorname")) {
+ // Break apart the comma-delimited list.
+ const char *pszValue = value;
+ const char *pszComma = strchr(pszValue, ',');
+ int i;
+ for (i = 0; pszValue != NULL && *pszValue && i < MAX_NUM_INTERFACE_MIRRORS; ++i) {
+ if (pszComma != NULL) {
+ // Copy the data up to the comma.
+ int copylen = (pszComma - pszValue < sizeof(pCfg->ifmirrorname[0]) - 1 ? pszComma - pszValue : sizeof(pCfg->ifmirrorname[0]) - 1);
+ strncpy(pCfg->ifmirrorname[i], pszValue, copylen);
+
+ // Iterate to the next string.
+ pszValue = pszComma + 1;
+ while (isspace(*pszValue)) pszValue++;
+ pszComma = strchr(pszValue, ',');
+ }
+ else {
+ // Copy the rest of the data.
+ strncpy(pCfg->ifmirrorname[i], pszValue, sizeof(pCfg->ifmirrorname[0]) - 1);
+
+ // Iterate to the next string.
+ pszValue = NULL;
+ }
+ }
+ if (i >= MAX_NUM_INTERFACE_MIRRORS && pszValue != NULL) {
+ AVB_LOGF_ERROR("Maximum of %d mirror interfaces are allowed", MAX_NUM_INTERFACE_MIRRORS);
+ }
+ else {
+ if (AVB_LOG_LEVEL_DEBUG <= AVB_LOG_LEVEL) {
+ for (i = 0; pCfg->ifmirrorname[i][0] && i < MAX_NUM_INTERFACE_MIRRORS; ++i) {
+ AVB_LOGF_DEBUG(" Mirror interface #%d: \"%s\"", i, pCfg->ifmirrorname[i]);
+ }
+ }
+ valOK = TRUE;
+ }
+ }
else if (MATCH(name, "vlan_id")) {
errno = 0;
long tmp;
diff --git a/lib/avtp_pipeline/tl/openavb_talker.c b/lib/avtp_pipeline/tl/openavb_talker.c
index 24fdacdd..171a20b1 100644
--- a/lib/avtp_pipeline/tl/openavb_talker.c
+++ b/lib/avtp_pipeline/tl/openavb_talker.c
@@ -2,16 +2,16 @@
Copyright (c) 2012-2015, Symphony Teleca Corporation, a Harman International Industries, Incorporated company
Copyright (c) 2016-2017, Harman International Industries, Incorporated
All rights reserved.
-
+
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
-
+
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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 LISTED "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
@@ -22,10 +22,10 @@ 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.
-
-Attributions: The inih library portion of the source code is licensed from
-Brush Technology and Ben Hoyt - Copyright (c) 2009, Brush Technology and Copyright (c) 2009, Ben Hoyt.
-Complete license and copyright information can be found at
+
+Attributions: The inih library portion of the source code is licensed from
+Brush Technology and Ben Hoyt - Copyright (c) 2009, Brush Technology and Copyright (c) 2009, Ben Hoyt.
+Complete license and copyright information can be found at
https://github.com/benhoyt/inih/commit/74d2ca064fb293bc60a77b0bd068075b293cf175.
*************************************************************************************************************/
@@ -84,10 +84,18 @@ bool talkerStartStream(tl_state_t *pTLState)
AVB_LOG_ERROR("Fixed timestamp enabled but interface doesn't support it");
}
+ // Create a list of the network interface names.
+ char* (ifnamearg[MAX_NUM_INTERFACE_MIRRORS + 2]) = { 0 };
+ int i;
+ ifnamearg[0] = pTalkerData->ifname;
+ for (i = 0; pCfg->ifmirrorname[i][0] && i < MAX_NUM_INTERFACE_MIRRORS; ++i) {
+ ifnamearg[i + 1] = pCfg->ifmirrorname[i];
+ }
+
openavbRC rc = openavbAvtpTxInit(pTLState->pMediaQ,
&pCfg->map_cb,
&pCfg->intf_cb,
- pTalkerData->ifname,
+ ifnamearg,
&pTalkerData->streamID,
pTalkerData->destAddr,
pCfg->max_transit_usec,
@@ -277,7 +285,7 @@ static inline bool talkerDoStream(tl_state_t *pTLState)
if (pCfg->report_seconds > 0) {
if (nowNS > pTalkerData->nextReportNS) {
talkerShowStats(pTalkerData, pTLState);
-
+
openavbTalkerAddStat(pTLState, TL_STAT_TX_CALLS, pTalkerData->cntWakes);
openavbTalkerAddStat(pTLState, TL_STAT_TX_FRAMES, pTalkerData->cntFrames);
@@ -301,11 +309,11 @@ static inline bool talkerDoStream(tl_state_t *pTLState)
pTalkerData->nextCycleNS += pTalkerData->intervalNS;
if ((pTalkerData->nextCycleNS + (pCfg->max_transmit_deficit_usec * 1000)) < nowNS) {
- // Hit max deficit time. Something must be wrong. Reset the cycle timer.
+ // Hit max deficit time. Something must be wrong. Reset the cycle timer.
// Align clock : allows for some performance gain
nowNS = ((nowNS + (pTalkerData->intervalNS)) / pTalkerData->intervalNS) * pTalkerData->intervalNS;
pTalkerData->nextCycleNS = nowNS + pTalkerData->intervalNS;
- }
+ }
}
}
else {
@@ -320,7 +328,7 @@ static inline bool talkerDoStream(tl_state_t *pTLState)
}
-// Called from openavbTLThreadFn() which is started from openavbTLRun()
+// Called from openavbTLThreadFn() which is started from openavbTLRun()
void openavbTLRunTalker(tl_state_t *pTLState)
{
AVB_TRACE_ENTRY(AVB_TRACE_TL);
@@ -350,7 +358,7 @@ void openavbTLRunTalker(tl_state_t *pTLState)
/* If using endpoint register talker,
else register with tpsec */
- pTLState->bConnected = openavbTLRunTalkerInit(pTLState);
+ pTLState->bConnected = openavbTLRunTalkerInit(pTLState);
if (pTLState->bConnected) {
bool bServiceIPC;
diff --git a/lib/avtp_pipeline/tl/openavb_tl_pub.h b/lib/avtp_pipeline/tl/openavb_tl_pub.h
index a5f3da69..028e8524 100755
--- a/lib/avtp_pipeline/tl/openavb_tl_pub.h
+++ b/lib/avtp_pipeline/tl/openavb_tl_pub.h
@@ -81,6 +81,9 @@ typedef enum {
/// Maximum size of the friendly name
#define FRIENDLY_NAME_SIZE 64
+/// Maximum number of interfaces to mirror the AVTP traffic to.
+#define MAX_NUM_INTERFACE_MIRRORS 4
+
/// Initial talker/listener state
typedef enum {
/// Unspecified
@@ -142,6 +145,8 @@ typedef struct {
bool tx_blocking_in_intf;
/// Network interface name. Not used on all platforms.
char ifname[IFNAMSIZ + 10]; // Include space for the socket type prefix (e.g. "simple:eth0")
+ /// Network interface name for interfaces to TX mirror the AVTP traffic to. Not used on all platforms.
+ char ifmirrorname[MAX_NUM_INTERFACE_MIRRORS][IFNAMSIZ + 10]; // Include space for the socket type prefix (e.g. "simple:eth0")
/// VLAN ID
U16 vlan_id;
/// When set incoming packets will trigger a signal to the stream task to wakeup.