598 lines
19 KiB
C++
598 lines
19 KiB
C++
//=========================================================================
|
|
// Name: FlexVitaTask.cpp
|
|
// Purpose: Audio handler for Flex waveform.
|
|
//
|
|
// Authors: Mooneer Salem
|
|
// License:
|
|
//
|
|
// All rights reserved.
|
|
//
|
|
// 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 OWNER
|
|
// 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.
|
|
//
|
|
//=========================================================================
|
|
|
|
#include <future>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <unistd.h>
|
|
#include <inttypes.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
|
|
#include "../pipeline/pipeline_defines.h"
|
|
#include "flex_defines.h"
|
|
#include "FlexVitaTask.h"
|
|
#include "FlexKeyValueParser.h"
|
|
#include "../util/logging/ulog.h"
|
|
|
|
constexpr float SHORT_TO_FLOAT_DIVIDER = 32767.0;
|
|
constexpr short FLOAT_TO_SHORT_MULTIPLIER = 32767;
|
|
const float TX_SCALE_FACTOR = expf(3.0f/20.0f * logf(10.0f));
|
|
|
|
using namespace std::placeholders;
|
|
|
|
FlexVitaTask::FlexVitaTask(std::shared_ptr<IRealtimeHelper> helper, float volumeAdjustmentDecibel)
|
|
: ThreadedObject("FlexVita")
|
|
, socket_(-1)
|
|
, discoverySocket_(-1)
|
|
, rxStreamId_(0)
|
|
, txStreamId_(0)
|
|
, audioSeqNum_(0)
|
|
, timeFracSeq_(0)
|
|
, audioEnabled_(false)
|
|
, isTransmitting_(false)
|
|
, inputCtr_(0)
|
|
, samplesRequired_(0)
|
|
, helper_(std::move(helper))
|
|
, volumeAdjustmentScaleFactor_(expf(volumeAdjustmentDecibel/20.0f * logf(10.0f)))
|
|
{
|
|
packetArray_ = new vita_packet[MAX_VITA_PACKETS];
|
|
assert(packetArray_ != nullptr);
|
|
packetIndex_ = 0;
|
|
|
|
// Allocate FIFOs for use by pipelines.
|
|
callbackData_.infifo1 = new GenericFIFO<short>(FIFO_SIZE_SAMPLES);
|
|
assert(callbackData_.infifo1 != nullptr);
|
|
callbackData_.infifo2 = new GenericFIFO<short>(FIFO_SIZE_SAMPLES);
|
|
assert(callbackData_.infifo1 != nullptr);
|
|
callbackData_.outfifo1 = new GenericFIFO<short>(FIFO_SIZE_SAMPLES);
|
|
assert(callbackData_.infifo1 != nullptr);
|
|
callbackData_.outfifo2 = new GenericFIFO<short>(FIFO_SIZE_SAMPLES);
|
|
assert(callbackData_.infifo1 != nullptr);
|
|
|
|
openSocket_();
|
|
}
|
|
|
|
FlexVitaTask::~FlexVitaTask()
|
|
{
|
|
auto prom = std::make_shared<std::promise<void>>();
|
|
auto fut = prom->get_future();
|
|
enqueue_([&, prom]() {
|
|
disconnect_();
|
|
prom->set_value();
|
|
});
|
|
fut.wait();
|
|
|
|
waitForAllTasksComplete_();
|
|
|
|
delete[] packetArray_;
|
|
delete callbackData_.infifo1;
|
|
delete callbackData_.infifo2;
|
|
delete callbackData_.outfifo1;
|
|
delete callbackData_.outfifo2;
|
|
}
|
|
|
|
void FlexVitaTask::sendMeter(uint16_t meterId, float valueDb)
|
|
{
|
|
enqueue_([this, meterId, valueDb]() {
|
|
// Get free packet
|
|
vita_packet* packet = &packetArray_[packetIndex_++];
|
|
assert(packet != nullptr);
|
|
if (packetIndex_ == MAX_VITA_PACKETS)
|
|
{
|
|
packetIndex_ = 0;
|
|
}
|
|
|
|
// Fil in packet with data
|
|
packet->packet_type = VITA_PACKET_TYPE_EXT_DATA_WITH_STREAM_ID;
|
|
packet->stream_id = METER_STREAM_ID;
|
|
packet->class_id = METER_CLASS_ID;
|
|
packet->timestamp_type = 0;
|
|
packet->timestamp_int = 0;
|
|
packet->timestamp_frac = 0;
|
|
packet->meter[0].id = htons(meterId);
|
|
packet->meter[0].value = htons((int32_t)(valueDb * 128) & 0xFFFF);
|
|
|
|
size_t packet_len = VITA_PACKET_HEADER_SIZE + sizeof(uint32_t);
|
|
packet->length = htons(packet_len >> 2); // Length is in 32-bit words
|
|
|
|
int rv = send(socket_, (char*)packet, packet_len, 0);
|
|
if (rv < 0)
|
|
{
|
|
// TBD: close and reopen socket
|
|
constexpr int ERROR_BUFFER_LEN = 1024;
|
|
char tmpBuf[ERROR_BUFFER_LEN];
|
|
log_error("Got socket error %d (%s) while sending", errno, strerror_r(errno, tmpBuf, ERROR_BUFFER_LEN));
|
|
}
|
|
});
|
|
}
|
|
|
|
GenericFIFO<short>* FlexVitaTask::getAudioInput_(bool tx)
|
|
{
|
|
return tx ? callbackData_.infifo1 : callbackData_.infifo2;
|
|
}
|
|
|
|
GenericFIFO<short>* FlexVitaTask::getAudioOutput_(bool tx)
|
|
{
|
|
return tx ? callbackData_.outfifo1 : callbackData_.outfifo2;
|
|
}
|
|
|
|
void FlexVitaTask::generateVitaPackets_(bool transmitChannel, uint32_t streamId)
|
|
{
|
|
short inputBuffer[MAX_VITA_SAMPLES];
|
|
|
|
auto fifo = getAudioOutput_(transmitChannel);
|
|
|
|
int ctr = 1;
|
|
while(ctr > 0 && fifo->read(inputBuffer, samplesRequired_) == 0)
|
|
{
|
|
ctr--;
|
|
|
|
if (!audioEnabled_)
|
|
{
|
|
// Skip sending audio to SmartSDR if the user isn't using us yet.
|
|
continue;
|
|
}
|
|
|
|
// Get free packet
|
|
vita_packet* packet = &packetArray_[packetIndex_++];
|
|
assert(packet != nullptr);
|
|
if (packetIndex_ == MAX_VITA_PACKETS)
|
|
{
|
|
packetIndex_ = 0;
|
|
}
|
|
uint32_t* ptrOut = (uint32_t*)packet->if_samples;
|
|
|
|
// Convert short to float samples and save to packet.
|
|
// Amplify signal as required.
|
|
for (int index = 0; index < samplesRequired_; index++)
|
|
{
|
|
float fpSample = inputBuffer[index] / SHORT_TO_FLOAT_DIVIDER;
|
|
if (transmitChannel) fpSample *= TX_SCALE_FACTOR;
|
|
else fpSample *= volumeAdjustmentScaleFactor_;
|
|
uint32_t* fpSampleAsInt = (uint32_t*)&fpSample;
|
|
uint32_t tmp = htonl(*fpSampleAsInt);
|
|
*ptrOut++ = tmp;
|
|
*ptrOut++ = tmp;
|
|
}
|
|
|
|
|
|
// Fil in packet with data
|
|
packet->packet_type = VITA_PACKET_TYPE_IF_DATA_WITH_STREAM_ID;
|
|
packet->stream_id = streamId;
|
|
packet->class_id = AUDIO_CLASS_ID;
|
|
packet->timestamp_type = audioSeqNum_++;
|
|
size_t packet_len = VITA_PACKET_HEADER_SIZE + samplesRequired_ * 2 * sizeof(float);
|
|
|
|
constexpr uint8_t FRACTIONAL_TIMESTAMP_REAL_TIME = 0x02;
|
|
constexpr uint8_t INTEGER_TIMESTAMP_UTC = 0x01;
|
|
packet->timestamp_type = (((FRACTIONAL_TIMESTAMP_REAL_TIME << 2) | INTEGER_TIMESTAMP_UTC) << 4) | (packet->timestamp_type & 0x0Fu);
|
|
|
|
assert((packet_len & 0x3) == 0); // equivalent to packet_len / 4
|
|
|
|
packet->length = htons(packet_len >> 2); // Length is in 32-bit words, note there are two channels
|
|
|
|
struct timespec currentTime = { .tv_sec = 0, .tv_nsec = 0 };
|
|
if (clock_gettime(CLOCK_REALTIME, ¤tTime) == -1)
|
|
{
|
|
log_warn("Could not get current time");
|
|
currentTime.tv_sec = 0;
|
|
currentTime.tv_nsec = 0;
|
|
}
|
|
|
|
packet->timestamp_int = htonl(currentTime.tv_sec);
|
|
packet->timestamp_frac = __builtin_bswap64(currentTime.tv_nsec * 1000);
|
|
|
|
int rv = send(socket_, (char*)packet, packet_len, 0);
|
|
if (rv < 0)
|
|
{
|
|
// TBD: close and reopen socket
|
|
constexpr int ERROR_BUFFER_LEN = 1024;
|
|
char tmpBuf[ERROR_BUFFER_LEN];
|
|
log_error("Got socket error %d (%s) while sending", errno, strerror_r(errno, tmpBuf, ERROR_BUFFER_LEN));
|
|
}
|
|
}
|
|
}
|
|
|
|
void FlexVitaTask::openSocket_()
|
|
{
|
|
constexpr int ERROR_BUFFER_LEN = 1024;
|
|
char tmpBuf[ERROR_BUFFER_LEN];
|
|
|
|
// Bind socket so we can at least get discovery packets.
|
|
socket_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (socket_ == -1)
|
|
{
|
|
log_error("Got socket error %d (%s) while creating socket", errno, strerror_r(errno, tmpBuf, ERROR_BUFFER_LEN));
|
|
assert(socket_ != -1);
|
|
return;
|
|
}
|
|
|
|
discoverySocket_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (discoverySocket_ == -1)
|
|
{
|
|
log_error("Got socket error %d (%s) while creating discovery socket", errno, strerror_r(errno, tmpBuf, ERROR_BUFFER_LEN));
|
|
assert(socket_ != -1);
|
|
return;
|
|
}
|
|
|
|
const int enable = 1;
|
|
if (setsockopt(discoverySocket_, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
|
|
{
|
|
log_warn("Could not set SO_REUSEADDR, other applications may not allow us to listen on the discovery port!");
|
|
}
|
|
|
|
// Bind to discovery port (4992)
|
|
struct sockaddr_in ourSocketAddress;
|
|
memset((char *) &ourSocketAddress, 0, sizeof(ourSocketAddress));
|
|
ourSocketAddress.sin_family = AF_INET;
|
|
ourSocketAddress.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
ourSocketAddress.sin_port = htons(VITA_PORT);
|
|
|
|
auto rv = bind(discoverySocket_, (struct sockaddr*)&ourSocketAddress, sizeof(ourSocketAddress));
|
|
if (rv == -1)
|
|
{
|
|
auto err = errno;
|
|
log_error("Got socket error %d (%s) while binding", err, strerror_r(err, tmpBuf, ERROR_BUFFER_LEN));
|
|
}
|
|
assert(rv != -1);
|
|
|
|
// Get local port for main socket
|
|
memset((char *) &ourSocketAddress, 0, sizeof(ourSocketAddress));
|
|
ourSocketAddress.sin_family = AF_INET;
|
|
ourSocketAddress.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
ourSocketAddress.sin_port = htons(0);
|
|
rv = bind(socket_, (struct sockaddr*)&ourSocketAddress, sizeof(ourSocketAddress));
|
|
if (rv == -1)
|
|
{
|
|
auto err = errno;
|
|
log_error("Got socket error %d (%s) while binding", err, strerror_r(err, tmpBuf, ERROR_BUFFER_LEN));
|
|
}
|
|
assert(rv != -1);
|
|
|
|
socklen_t bindAddrLen = sizeof(ourSocketAddress);
|
|
memset((char *) &ourSocketAddress, 0, sizeof(ourSocketAddress));
|
|
rv = getsockname(socket_, (struct sockaddr*) &ourSocketAddress, &bindAddrLen);
|
|
if (rv == -1)
|
|
{
|
|
auto err = errno;
|
|
log_error("Got socket error %d (%s) while calling getsockname", err, strerror_r(err, tmpBuf, ERROR_BUFFER_LEN));
|
|
}
|
|
assert(rv != -1);
|
|
|
|
udpPort_ = ntohs(ourSocketAddress.sin_port);
|
|
|
|
fcntl (socket_, F_SETFL , O_NONBLOCK);
|
|
fcntl (discoverySocket_, F_SETFL , O_NONBLOCK);
|
|
|
|
rxTxThreadRunning_ = true;
|
|
rxTxThread_ = std::thread(std::bind(&FlexVitaTask::rxTxThreadEntry_, this));
|
|
|
|
inputCtr_ = 0;
|
|
}
|
|
|
|
void FlexVitaTask::disconnect_()
|
|
{
|
|
rxTxThreadRunning_ = false;
|
|
if (rxTxThread_.joinable())
|
|
{
|
|
rxTxThread_.join();
|
|
}
|
|
|
|
if (socket_ > 0)
|
|
{
|
|
close(socket_);
|
|
socket_ = -1;
|
|
|
|
if (discoverySocket_ > -1)
|
|
{
|
|
close(discoverySocket_);
|
|
discoverySocket_ = -1;
|
|
}
|
|
|
|
rxStreamId_ = 0;
|
|
txStreamId_ = 0;
|
|
audioSeqNum_ = 0;
|
|
timeFracSeq_ = 0;
|
|
inputCtr_ = 0;
|
|
packetIndex_ = 0;
|
|
}
|
|
}
|
|
|
|
void FlexVitaTask::rxTxThreadEntry_()
|
|
{
|
|
helper_->setHelperRealTime();
|
|
|
|
struct pollfd fds[2];
|
|
int numFds = 0;
|
|
|
|
while (rxTxThreadRunning_)
|
|
{
|
|
memset(fds, 0, sizeof(fds));
|
|
fds[0].fd = socket_;
|
|
fds[0].events = POLLIN;
|
|
|
|
if (discoverySocket_ > -1)
|
|
{
|
|
fds[1].fd = discoverySocket_;
|
|
fds[1].events = POLLIN;
|
|
numFds = 2;
|
|
}
|
|
else
|
|
{
|
|
numFds = 1;
|
|
}
|
|
|
|
if (poll(fds, numFds, VITA_IO_TIME_INTERVAL_US / 1000) > 0)
|
|
{
|
|
readPendingPackets_(fds, numFds);
|
|
}
|
|
}
|
|
|
|
helper_->clearHelperRealTime();
|
|
}
|
|
|
|
void FlexVitaTask::readPendingPackets_(struct pollfd* fds, int numFds)
|
|
{
|
|
// Process if there are pending datagrams in the buffer
|
|
int ctr = MAX_VITA_PACKETS_TO_SEND;
|
|
while (ctr-- > 0)
|
|
{
|
|
vita_packet* packet = &packetArray_[packetIndex_++];
|
|
assert(packet != nullptr);
|
|
|
|
if (packetIndex_ == MAX_VITA_PACKETS)
|
|
{
|
|
packetIndex_ = 0;
|
|
}
|
|
|
|
int rv = 0;
|
|
if (fds[0].revents != 0)
|
|
{
|
|
rv = recv(socket_, (char*)packet, sizeof(vita_packet), 0);
|
|
if (rv > 0)
|
|
{
|
|
// Queue up packet for future processing.
|
|
onReceiveVitaMessage_(packet, rv);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (numFds > 1 && fds[1].revents != 0)
|
|
{
|
|
packet = &packetArray_[packetIndex_++];
|
|
assert(packet != nullptr);
|
|
|
|
if (packetIndex_ == MAX_VITA_PACKETS)
|
|
{
|
|
packetIndex_ = 0;
|
|
}
|
|
|
|
rv = recv(discoverySocket_, (char*)packet, sizeof(vita_packet), 0);
|
|
if (rv > 0)
|
|
{
|
|
// Queue up packet for future processing.
|
|
onReceiveVitaMessage_(packet, rv);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FlexVitaTask::sendAudioOut_()
|
|
{
|
|
// Generate packets for both RX and TX.
|
|
if (rxStreamId_ && !isTransmitting_)
|
|
{
|
|
generateVitaPackets_(false, rxStreamId_);
|
|
}
|
|
else if (rxStreamId_)
|
|
{
|
|
// Clear FIFO if we're not in the right state. This is so that we
|
|
// don't end up with audio packets going to the wrong place
|
|
// (i.e. UI beeps being transmitted along with the FreeDV signal).
|
|
auto fifo = getAudioOutput_(false);
|
|
fifo->reset();
|
|
}
|
|
|
|
if (txStreamId_ && isTransmitting_)
|
|
{
|
|
generateVitaPackets_(true, txStreamId_);
|
|
}
|
|
else if (txStreamId_)
|
|
{
|
|
// Clear FIFO if we're not in the right state. This is so that we
|
|
// don't end up with audio packets going to the wrong place
|
|
// (i.e. UI beeps being transmitted along with the FreeDV signal).
|
|
auto fifo = getAudioOutput_(true);
|
|
fifo->reset();
|
|
}
|
|
}
|
|
|
|
void FlexVitaTask::clearStreamIds()
|
|
{
|
|
log_info("Clearing registered stream IDs");
|
|
|
|
txStreamIds_.clear();
|
|
rxStreamIds_.clear();
|
|
}
|
|
|
|
void FlexVitaTask::registerStreamIds(uint32_t txInStreamId, uint32_t txOutStreamId, uint32_t rxInStreamId, uint32_t rxOutStreamId)
|
|
{
|
|
log_info("Registering stream IDs: txin=%" PRIu32 " txout=%" PRIu32 " rxin=%" PRIu32 " rxout=%" PRIu32, txInStreamId, txOutStreamId, rxInStreamId, rxOutStreamId);
|
|
txStreamIds_[txInStreamId] = txOutStreamId;
|
|
rxStreamIds_[rxInStreamId] = rxOutStreamId;
|
|
}
|
|
|
|
void FlexVitaTask::radioConnected(const char* ip)
|
|
{
|
|
enqueue_([this, ip]() {
|
|
ip_ = ip;
|
|
|
|
radioAddress_.sin_addr.s_addr = inet_addr(ip_.c_str());
|
|
radioAddress_.sin_family = AF_INET;
|
|
radioAddress_.sin_port = htons(4993); // hardcoded as per Flex documentation
|
|
|
|
if (connect(socket_, (struct sockaddr*)&radioAddress_, sizeof(radioAddress_)) < 0)
|
|
{
|
|
log_error("Could not connect socket to radio's IP (errno = %d)", errno);
|
|
}
|
|
else
|
|
{
|
|
log_info("Connected to radio successfully");
|
|
}
|
|
|
|
// Close discovery socket as it's no longer needed
|
|
if (discoverySocket_ > -1)
|
|
{
|
|
close(discoverySocket_);
|
|
discoverySocket_ = -1;
|
|
}
|
|
});
|
|
}
|
|
|
|
void FlexVitaTask::onReceiveVitaMessage_(vita_packet* packet, int length)
|
|
{
|
|
// Make sure packet is long enough to inspect for VITA header info.
|
|
if ((unsigned)length < VITA_PACKET_HEADER_SIZE)
|
|
return;
|
|
|
|
// Make sure packet is from the radio.
|
|
if((packet->class_id & VITA_OUI_MASK) != FLEX_OUI)
|
|
return;
|
|
|
|
// Look for discovery packets
|
|
if (packet->stream_id == DISCOVERY_STREAM_ID && packet->class_id == DISCOVERY_CLASS_ID)
|
|
{
|
|
std::stringstream ss((char*)packet->raw_payload);
|
|
auto parameters = FlexKeyValueParser::GetCommandParameters(ss);
|
|
|
|
auto radioFriendlyName = parameters["nickname"] + " (" + parameters["callsign"] + ")";
|
|
auto radioIp = parameters["ip"];
|
|
|
|
log_info("Discovery: found radio %s at IP %s", radioFriendlyName.c_str(), radioIp.c_str());
|
|
|
|
if (onRadioDiscoveredFn_)
|
|
{
|
|
onRadioDiscoveredFn_(*this, radioFriendlyName, radioIp, onRadioDiscoveredFnState_);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
switch(packet->stream_id & STREAM_BITS_MASK)
|
|
{
|
|
case STREAM_BITS_WAVEFORM | STREAM_BITS_IN:
|
|
{
|
|
unsigned long payload_length = ((htons(packet->length) * sizeof(uint32_t)) - VITA_PACKET_HEADER_SIZE);
|
|
|
|
GenericFIFO<short>* inFifo = nullptr;
|
|
if (!(htonl(packet->stream_id) & 0x0001u))
|
|
{
|
|
// Packet contains receive audio from radio.
|
|
rxStreamId_ = rxStreamIds_[htonl(packet->stream_id)];
|
|
if (rxStreamId_ == 0) return;
|
|
rxStreamId_ = packet->stream_id;
|
|
inFifo = getAudioInput_(false);
|
|
}
|
|
else
|
|
{
|
|
// Packet contains transmit audio from user's microphone.
|
|
txStreamId_ = txStreamIds_[htonl(packet->stream_id)];
|
|
if (txStreamId_ == 0) return;
|
|
txStreamId_ = packet->stream_id;
|
|
//log_info("outputting on stream %08x, input on %08x", txStreamId_, packet->stream_id);
|
|
inFifo = getAudioInput_(true);
|
|
}
|
|
|
|
if (inFifo == nullptr)
|
|
{
|
|
// No valid FIFO, return
|
|
return;
|
|
}
|
|
|
|
// Convert to int16 samples, normalizing to +/- 1.0 beforehand.
|
|
unsigned int num_samples = payload_length >> 2; // / sizeof(uint32_t);
|
|
unsigned int half_num_samples = num_samples >> 1;
|
|
|
|
unsigned int i = 0;
|
|
short audioInput[MAX_VITA_SAMPLES];
|
|
float audioInputFloat[MAX_VITA_SAMPLES];
|
|
while (i < half_num_samples)
|
|
{
|
|
union {
|
|
uint32_t intVal;
|
|
float floatVal;
|
|
} temp;
|
|
temp.intVal = ntohl(packet->if_samples[i << 1]);
|
|
audioInputFloat[i++] = temp.floatVal;
|
|
}
|
|
for (i = 0; i < half_num_samples; i++)
|
|
{
|
|
audioInput[i] = tanhf(audioInputFloat[i]) * FLOAT_TO_SHORT_MULTIPLIER;
|
|
}
|
|
if (!pendingEndTx_)
|
|
{
|
|
inFifo->write(audioInput, half_num_samples); // audio pipeline will resample
|
|
}
|
|
samplesRequired_ = half_num_samples;
|
|
sendAudioOut_();
|
|
break;
|
|
}
|
|
default:
|
|
log_warn("Undefined stream in %lx", htonl(packet->stream_id));
|
|
break;
|
|
}
|
|
}
|
|
|
|
void FlexVitaTask::enableAudio(bool enabled)
|
|
{
|
|
enqueue_([this, enabled]() {
|
|
audioEnabled_ = enabled;
|
|
});
|
|
}
|
|
|
|
void FlexVitaTask::setTransmit(bool tx)
|
|
{
|
|
enqueue_([this, tx]() {
|
|
isTransmitting_ = tx;
|
|
});
|
|
}
|