support for dvsi 300{0,3}

main
Tom Early 4 years ago
parent 88f6aeba9e
commit 91f910ead0

@ -25,13 +25,11 @@
#include "TranscoderPacket.h" #include "TranscoderPacket.h"
#include "Controller.h" #include "Controller.h"
CController::CController() : keep_running(true) CController::CController() : keep_running(true) {}
{
}
bool CController::Start() bool CController::Start()
{ {
if (InitDevices() || reader.Open(REF2TC)) if (InitVocoders() || reader.Open(REF2TC))
{ {
keep_running = false; keep_running = false;
return true; return true;
@ -51,8 +49,10 @@ void CController::Stop()
c2Future.get(); c2Future.get();
reader.Close(); reader.Close();
dstar_device.CloseDevice(); dstar_device->CloseDevice();
dmrsf_device.CloseDevice(); dmrsf_device->CloseDevice();
dstar_device.reset();
dmrsf_device.reset();
} }
bool CController::CheckTCModules() const bool CController::CheckTCModules() const
@ -110,7 +110,7 @@ bool CController::DiscoverFtdiDevices(std::list<std::pair<std::string, std::stri
for ( int i = 0; i < iNbDevices; i++ ) for ( int i = 0; i < iNbDevices; i++ )
{ {
std::cout << "Found " << list[i].Description << ", SerialNo=" << list[i].SerialNumber << std::endl; std::cout << "Found " << list[i].Description << ", SN=" << list[i].SerialNumber << std::endl;
found.emplace_back(std::pair<std::string, std::string>(list[i].SerialNumber, list[i].Description)); found.emplace_back(std::pair<std::string, std::string>(list[i].SerialNumber, list[i].Description));
} }
@ -122,7 +122,7 @@ bool CController::DiscoverFtdiDevices(std::list<std::pair<std::string, std::stri
return false; return false;
} }
bool CController::InitDevices() bool CController::InitVocoders()
{ {
if (CheckTCModules()) if (CheckTCModules())
return true; return true;
@ -172,17 +172,35 @@ bool CController::InitDevices()
//initialize each device //initialize each device
while (! deviceset.empty()) while (! deviceset.empty())
{ {
if (dstar_device.OpenDevice(deviceset.front().first, deviceset.front().second, dvtype)) if (Edvtype::dv3000 == dvtype)
{
dstar_device = std::unique_ptr<CDVDevice>(new CDV3000(Encoding::dstar));
dmrsf_device = std::unique_ptr<CDVDevice>(new CDV3000(Encoding::dmrsf));
}
else
{
dstar_device = std::unique_ptr<CDVDevice>(new CDV3003(Encoding::dstar));
dmrsf_device = std::unique_ptr<CDVDevice>(new CDV3003(Encoding::dmrsf));
}
if (dstar_device && dmrsf_device)
{
if (dstar_device->OpenDevice(deviceset.front().first, deviceset.front().second, dvtype))
return true; return true;
deviceset.pop_front(); deviceset.pop_front();
if (dmrsf_device.OpenDevice(deviceset.front().first, deviceset.front().second, dvtype)) if (dmrsf_device->OpenDevice(deviceset.front().first, deviceset.front().second, dvtype))
return true; return true;
deviceset.pop_front(); deviceset.pop_front();
} }
else
{
std::cerr << "Could not create DVSI devices!" << std::endl;
return true;
}
}
// and start them up! // and start them up!
dstar_device.Start(); dstar_device->Start();
dmrsf_device.Start(); dmrsf_device->Start();
deviceset.clear(); deviceset.clear();
@ -206,10 +224,10 @@ void CController::ReadReflectorThread()
switch (packet->GetCodecIn()) switch (packet->GetCodecIn())
{ {
case ECodecType::dstar: case ECodecType::dstar:
dstar_device.AddPacket(packet); dstar_device->AddPacket(packet);
break; break;
case ECodecType::dmr: case ECodecType::dmr:
dmrsf_device.AddPacket(packet); dmrsf_device->AddPacket(packet);
break; break;
case ECodecType::c2_1600: case ECodecType::c2_1600:
case ECodecType::c2_3200: case ECodecType::c2_3200:
@ -302,8 +320,8 @@ void CController::Codec2toAudio(std::shared_ptr<CTranscoderPacket> packet)
} }
} }
// the only thing left is to encode the two ambe, so push the packet onto both AMBE queues // the only thing left is to encode the two ambe, so push the packet onto both AMBE queues
dstar_device.AddPacket(packet); dstar_device->AddPacket(packet);
dmrsf_device.AddPacket(packet); dmrsf_device->AddPacket(packet);
} }
void CController::ProcessC2Thread() void CController::ProcessC2Thread()
@ -348,7 +366,7 @@ void CController::RouteDstPacket(std::shared_ptr<CTranscoderPacket> packet)
{ {
// codec_in is dstar, the audio has just completed, so now calc the M17 and DMR // codec_in is dstar, the audio has just completed, so now calc the M17 and DMR
codec2_queue.push(packet); codec2_queue.push(packet);
dmrsf_device.AddPacket(packet); dmrsf_device->AddPacket(packet);
} }
else if (packet->AllCodecsAreSet()) else if (packet->AllCodecsAreSet())
{ {
@ -363,7 +381,7 @@ void CController::RouteDmrPacket(std::shared_ptr<CTranscoderPacket> packet)
if (ECodecType::dmr == packet->GetCodecIn()) if (ECodecType::dmr == packet->GetCodecIn())
{ {
codec2_queue.push(packet); codec2_queue.push(packet);
dstar_device.AddPacket(packet); dstar_device->AddPacket(packet);
} }
else if (packet->AllCodecsAreSet()) else if (packet->AllCodecsAreSet())
{ {

@ -25,7 +25,8 @@
#include <utility> #include <utility>
#include "codec2.h" #include "codec2.h"
#include "DVSIDevice.h" #include "DV3000.h"
#include "DV3003.h"
#include "UnixDgramSocket.h" #include "UnixDgramSocket.h"
#include "configure.h" #include "configure.h"
@ -49,14 +50,13 @@ protected:
CUnixDgramReader reader; CUnixDgramReader reader;
CUnixDgramWriter writer; CUnixDgramWriter writer;
std::unordered_map<char, std::unique_ptr<CCodec2>> c2_16, c2_32; std::unordered_map<char, std::unique_ptr<CCodec2>> c2_16, c2_32;
CDVDevice dstar_device{Encoding::dstar}; std::unique_ptr<CDVDevice> dstar_device, dmrsf_device;
CDVDevice dmrsf_device{Encoding::dmrsf};
CPacketQueue codec2_queue; CPacketQueue codec2_queue;
std::mutex send_mux; std::mutex send_mux;
bool DiscoverFtdiDevices(std::list<std::pair<std::string, std::string>> &found); bool DiscoverFtdiDevices(std::list<std::pair<std::string, std::string>> &found);
bool InitDevices(); bool InitVocoders();
// processing threads // processing threads
void ReadReflectorThread(); void ReadReflectorThread();
void ProcessC2Thread(); void ProcessC2Thread();

@ -0,0 +1,221 @@
/*
* Copyright (C) 2014 by Jonathan Naylor G4KLX and John Hays K7VE
* Copyright 2016 by Jeremy McDermond (NH6Z)
* Copyright 2021 by Thomas Early N7TAE
*
* Adapted by K7VE from G4KLX dv3000d
*/
// tcd - a hybid transcoder using DVSI hardware and Codec2 software
// Copyright © 2022 Thomas A. Early N7TAE
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <sys/select.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <iomanip>
#include <cerrno>
#include <thread>
#include "DV3000.h"
#include "configure.h"
#include "Controller.h"
extern CController Controller;
CDV3000::CDV3000(Encoding t) : CDVDevice(t) {}
CDV3000::~CDV3000() {}
void CDV3000::FeedDevice()
{
const std::string modules(TRANSCODED_MODULES);
const auto n = modules.size();
while (keep_running)
{
auto packet = input_queue.pop(); // blocks until there is something to pop
while (keep_running) // wait until there is room
{
if (buffer_depth < 2)
break;
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
if (keep_running)
{
auto index = modules.find(packet->GetModule());
// save the packet in the vocoder's queue while the vocoder does its magic
if (std::string::npos == index)
{
std::cerr << "Module '" << packet->GetModule() << "' is not configured on " << description << std::endl;
}
else
{
waiting_packet.push(packet);
const bool needs_audio = (Encoding::dstar==type) ? packet->DStarIsSet() : packet->DMRIsSet();
if (needs_audio)
{
SendData(index, (Encoding::dstar==type) ? packet->GetDStarData() : packet->GetDMRData());
}
else
{
SendAudio(index, packet->GetAudioSamples());
}
buffer_depth++;
}
}
}
}
void CDV3000::ReadDevice()
{
while (keep_running)
{
// wait for something to read...
DWORD RxBytes = 0;
while (0 == RxBytes)
{
EVENT_HANDLE eh;
pthread_mutex_init(&eh.eMutex, NULL);
pthread_cond_init(&eh.eCondVar, NULL);
DWORD EventMask = FT_EVENT_RXCHAR;
auto status = FT_SetEventNotification(ftHandle, EventMask, &eh);
if (FT_OK != status)
{
FTDI_Error("Setting Event Notification", status);
}
pthread_mutex_lock(&eh.eMutex);
pthread_cond_wait(&eh.eCondVar, &eh.eMutex);
pthread_mutex_unlock(&eh.eMutex);
DWORD EventDWord, TxBytes, Status;
status = FT_GetStatus(ftHandle, &RxBytes, &TxBytes, &EventDWord);
if (FT_OK != status)
{
FTDI_Error("Getting Event Status", status);
}
}
SDV_Packet p;
if (! GetResponse(p))
{
unsigned int channel = p.field_id - PKT_CHANNEL0;
auto packet = waiting_packet.pop();
if (PKT_CHANNEL == p.header.packet_type)
{
if (12!=ntohs(p.header.payload_length) || PKT_CHAND!=p.payload.ambe.chand || 72!=p.payload.ambe.num_bits)
dump("Improper ambe packet:", &p, packet_size(p));
buffer_depth--;
if (Encoding::dstar == type)
packet->SetDStarData(p.payload.ambe.data);
else
packet->SetDMRData(p.payload.ambe.data);
}
else if (PKT_SPEECH == p.header.packet_type)
{
if (323!=ntohs(p.header.payload_length) || PKT_SPEECHD!=p.payload.audio.speechd || 160!=p.payload.audio.num_samples)
dump("Improper audio packet:", &p, packet_size(p));
buffer_depth--;
packet->SetAudioSamples(p.payload.audio.samples, true);
}
else
{
dump("ReadDevice() ERROR: Read an unexpected device packet:", &p, packet_size(p));
continue;
}
if (Encoding::dstar == type) // is this a DMR or a DStar device?
{
Controller.dstar_mux.lock();
Controller.RouteDstPacket(packet);
Controller.dstar_mux.unlock();
}
else
{
Controller.dmrst_mux.lock();
Controller.RouteDmrPacket(packet);
Controller.dmrst_mux.unlock();
}
}
}
}
bool CDV3000::SendAudio(const uint8_t /*channel*/, const int16_t *audio) const
{
// Create Audio packet based on input int8_ts
SDV_Packet p;
p.start_byte = PKT_HEADER;
p.header.payload_length = htons(1 + sizeof(p.payload.audio3k));
p.header.packet_type = PKT_SPEECH;
p.field_id = PKT_SPEECHD;
p.payload.audio3k.num_samples = 160U;
for (int i=0; i<160; i++)
p.payload.audio3k.samples[i] = htons(audio[i]);
// send audio packet to DV3000
const DWORD size = packet_size(p);
DWORD written;
auto status = FT_Write(ftHandle, &p, size, &written);
if (FT_OK != status)
{
FTDI_Error("Error writing audio packet", status);
return true;
}
else if (size != written)
{
std::cerr << "Incomplete Speech Packet write on " << description << std::endl;
return true;
}
return false;
}
bool CDV3000::SendData(const uint8_t /* channel */, const uint8_t *data) const
{
// Create data packet
SDV_Packet p;
p.start_byte = PKT_HEADER;
p.header.payload_length = htons(1 + sizeof(p.payload.ambe3k));
p.header.packet_type = PKT_CHANNEL;
p.field_id = PKT_CHAND;
p.payload.ambe3k.num_bits = 72U;
memcpy(p.payload.ambe3k.data, data, 9);
// send data packet to DV3000
const DWORD size = packet_size(p);
DWORD written;
auto status = FT_Write(ftHandle, &p, size, &written);
if (FT_OK != status)
{
FTDI_Error("Error writing AMBE Packet", status);
return true;
}
else if (size != written)
{
std::cerr << "Incomplete AMBE Packet write on " << description << std::endl;
return true;
}
return false;
}

@ -0,0 +1,36 @@
#pragma once
// tcd - a hybid transcoder using DVSI hardware and Codec2 software
// Copyright © 2022 Thomas A. Early N7TAE
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "DVSIDevice.h"
class CDV3000 : public CDVDevice
{
public:
CDV3000(Encoding t);
virtual ~CDV3000();
protected:
void FeedDevice();
void ReadDevice();
bool SendAudio(const uint8_t channel, const int16_t *audio) const;
bool SendData(const uint8_t channel, const uint8_t *data) const;
private:
CPacketQueue waiting_packet; // the packet currently being processed in each vocoder
};

@ -0,0 +1,223 @@
/*
* Copyright (C) 2014 by Jonathan Naylor G4KLX and John Hays K7VE
* Copyright 2016 by Jeremy McDermond (NH6Z)
* Copyright 2021 by Thomas Early N7TAE
*
* Adapted by K7VE from G4KLX dv3000d
*/
// tcd - a hybid transcoder using DVSI hardware and Codec2 software
// Copyright © 2022 Thomas A. Early N7TAE
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <sys/select.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <iomanip>
#include <cerrno>
#include <thread>
#include "DV3003.h"
#include "configure.h"
#include "Controller.h"
extern CController Controller;
CDV3003::CDV3003(Encoding t) : CDVDevice(t) {}
CDV3003::~CDV3003() {}
void CDV3003::FeedDevice()
{
const std::string modules(TRANSCODED_MODULES);
const auto n = modules.size();
while (keep_running)
{
auto packet = input_queue.pop(); // blocks until there is something to pop
while (keep_running) // wait until there is room
{
if (buffer_depth < 2)
break;
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
if (keep_running)
{
auto index = modules.find(packet->GetModule());
// save the packet in the vocoder's queue while the vocoder does its magic
if (std::string::npos == index)
{
std::cerr << "Module '" << packet->GetModule() << "' is not configured on " << description << std::endl;
}
else
{
waiting_packet[index].push(packet);
const bool needs_audio = (Encoding::dstar==type) ? packet->DStarIsSet() : packet->DMRIsSet();
if (needs_audio)
{
SendData(index, (Encoding::dstar==type) ? packet->GetDStarData() : packet->GetDMRData());
}
else
{
SendAudio(index, packet->GetAudioSamples());
}
buffer_depth++;
}
}
}
}
void CDV3003::ReadDevice()
{
while (keep_running)
{
// wait for something to read...
DWORD RxBytes = 0;
while (0 == RxBytes)
{
EVENT_HANDLE eh;
pthread_mutex_init(&eh.eMutex, NULL);
pthread_cond_init(&eh.eCondVar, NULL);
DWORD EventMask = FT_EVENT_RXCHAR;
auto status = FT_SetEventNotification(ftHandle, EventMask, &eh);
if (FT_OK != status)
{
FTDI_Error("Setting Event Notification", status);
}
pthread_mutex_lock(&eh.eMutex);
pthread_cond_wait(&eh.eCondVar, &eh.eMutex);
pthread_mutex_unlock(&eh.eMutex);
DWORD EventDWord, TxBytes, Status;
status = FT_GetStatus(ftHandle, &RxBytes, &TxBytes, &EventDWord);
if (FT_OK != status)
{
FTDI_Error("Getting Event Status", status);
}
}
SDV_Packet p;
if (! GetResponse(p))
{
unsigned int channel = p.field_id - PKT_CHANNEL0;
auto packet = waiting_packet[channel].pop();
if (PKT_CHANNEL == p.header.packet_type)
{
if (12!=ntohs(p.header.payload_length) || PKT_CHAND!=p.payload.ambe.chand || 72!=p.payload.ambe.num_bits)
dump("Improper ambe packet:", &p, packet_size(p));
buffer_depth--;
if (Encoding::dstar == type)
packet->SetDStarData(p.payload.ambe.data);
else
packet->SetDMRData(p.payload.ambe.data);
}
else if (PKT_SPEECH == p.header.packet_type)
{
if (323!=ntohs(p.header.payload_length) || PKT_SPEECHD!=p.payload.audio.speechd || 160!=p.payload.audio.num_samples)
dump("Improper audio packet:", &p, packet_size(p));
buffer_depth--;
packet->SetAudioSamples(p.payload.audio.samples, true);
}
else
{
dump("ReadDevice() ERROR: Read an unexpected device packet:", &p, packet_size(p));
continue;
}
if (Encoding::dstar == type) // is this a DMR or a DStar device?
{
Controller.dstar_mux.lock();
Controller.RouteDstPacket(packet);
Controller.dstar_mux.unlock();
}
else
{
Controller.dmrst_mux.lock();
Controller.RouteDmrPacket(packet);
Controller.dmrst_mux.unlock();
}
}
}
}
bool CDV3003::SendAudio(const uint8_t channel, const int16_t *audio) const
{
// Create Audio packet based on input int8_ts
SDV_Packet p;
p.start_byte = PKT_HEADER;
p.header.payload_length = htons(1 + sizeof(p.payload.audio));
p.header.packet_type = PKT_SPEECH;
p.field_id = channel + PKT_CHANNEL0;
p.payload.audio.speechd = PKT_SPEECHD;
p.payload.audio.num_samples = 160U;
for (int i=0; i<160; i++)
p.payload.audio.samples[i] = htons(audio[i]);
// send audio packet to DV3000
const DWORD size = packet_size(p);
DWORD written;
auto status = FT_Write(ftHandle, &p, size, &written);
if (FT_OK != status)
{
FTDI_Error("Error writing audio packet", status);
return true;
}
else if (size != written)
{
std::cerr << "Incomplete Speech Packet write on " << description << std::endl;
return true;
}
return false;
}
bool CDV3003::SendData(const uint8_t channel, const uint8_t *data) const
{
// Create data packet
SDV_Packet p;
p.start_byte = PKT_HEADER;
p.header.payload_length = htons(1 + sizeof(p.payload.ambe));
p.header.packet_type = PKT_CHANNEL;
p.field_id = channel + PKT_CHANNEL0;
p.payload.ambe.chand = PKT_CHAND;
p.payload.ambe.num_bits = 72U;
memcpy(p.payload.ambe.data, data, 9);
// send data packet to DV3000
const DWORD size = packet_size(p);
DWORD written;
auto status = FT_Write(ftHandle, &p, size, &written);
if (FT_OK != status)
{
FTDI_Error("Error writing AMBE Packet", status);
return true;
}
else if (size != written)
{
std::cerr << "Incomplete AMBE Packet write on " << description << std::endl;
return true;
}
return false;
}

@ -0,0 +1,36 @@
#pragma once
// tcd - a hybid transcoder using DVSI hardware and Codec2 software
// Copyright © 2022 Thomas A. Early N7TAE
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "DVSIDevice.h"
class CDV3003 : public CDVDevice
{
public:
CDV3003(Encoding t);
virtual ~CDV3003();
protected:
void FeedDevice();
void ReadDevice();
bool SendAudio(const uint8_t channel, const int16_t *audio) const;
bool SendData(const uint8_t channel, const uint8_t *data) const;
private:
CPacketQueue waiting_packet[3]; // the packet currently being processed in each vocoder
};

@ -140,11 +140,6 @@ bool CDVDevice::checkResponse(SDV_Packet &p, uint8_t response) const
return false; return false;
} }
std::string CDVDevice::GetDescription() const
{
return description;
}
bool CDVDevice::OpenDevice(const std::string &serialno, const std::string &desc, Edvtype dvtype) bool CDVDevice::OpenDevice(const std::string &serialno, const std::string &desc, Edvtype dvtype)
{ {
auto status = FT_OpenEx((PVOID)serialno.c_str(), FT_OPEN_BY_SERIAL_NUMBER, &ftHandle); auto status = FT_OpenEx((PVOID)serialno.c_str(), FT_OPEN_BY_SERIAL_NUMBER, &ftHandle);
@ -232,7 +227,7 @@ bool CDVDevice::OpenDevice(const std::string &serialno, const std::string &desc,
// } // }
description.assign(desc); description.assign(desc);
description.append(": "); description.append("-");
description.append(serialno); description.append(serialno);
std::cout << "Opened " << description << std::endl; std::cout << "Opened " << description << std::endl;
@ -512,192 +507,11 @@ bool CDVDevice::GetResponse(SDV_Packet &packet)
return false; return false;
} }
void CDVDevice::FeedDevice()
{
const std::string modules(TRANSCODED_MODULES);
const auto n = modules.size();
while (keep_running)
{
auto packet = input_queue.pop(); // blocks until there is something to pop
while (keep_running) // wait until there is room
{
if (buffer_depth < 2)
break;
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
if (keep_running)
{
auto index = modules.find(packet->GetModule());
// save the packet in the vocoder's queue while the vocoder does its magic
if (std::string::npos == index)
{
std::cerr << "Module '" << packet->GetModule() << "' is not configured on " << description << std::endl;
}
else
{
waiting_packet[index].push(packet);
const bool needs_audio = (Encoding::dstar==type) ? packet->DStarIsSet() : packet->DMRIsSet();
if (needs_audio)
{
SendData(index, (Encoding::dstar==type) ? packet->GetDStarData() : packet->GetDMRData());
}
else
{
SendAudio(index, packet->GetAudioSamples());
}
buffer_depth++;
}
}
}
}
void CDVDevice::ReadDevice()
{
while (keep_running)
{
// wait for something to read...
DWORD RxBytes = 0;
while (0 == RxBytes)
{
EVENT_HANDLE eh;
pthread_mutex_init(&eh.eMutex, NULL);
pthread_cond_init(&eh.eCondVar, NULL);
DWORD EventMask = FT_EVENT_RXCHAR;
auto status = FT_SetEventNotification(ftHandle, EventMask, &eh);
if (FT_OK != status)
{
FTDI_Error("Setting Event Notification", status);
}
pthread_mutex_lock(&eh.eMutex);
pthread_cond_wait(&eh.eCondVar, &eh.eMutex);
pthread_mutex_unlock(&eh.eMutex);
DWORD EventDWord, TxBytes, Status;
status = FT_GetStatus(ftHandle, &RxBytes, &TxBytes, &EventDWord);
if (FT_OK != status)
{
FTDI_Error("Getting Event Status", status);
}
}
SDV_Packet p;
if (! GetResponse(p))
{
unsigned int channel = p.field_id - PKT_CHANNEL0;
auto packet = waiting_packet[channel].pop();
if (PKT_CHANNEL == p.header.packet_type)
{
if (12!=ntohs(p.header.payload_length) || PKT_CHAND!=p.payload.ambe.chand || 72!=p.payload.ambe.num_bits)
dump("Improper ambe packet:", &p, packet_size(p));
buffer_depth--;
if (Encoding::dstar == type)
packet->SetDStarData(p.payload.ambe.data);
else
packet->SetDMRData(p.payload.ambe.data);
}
else if (PKT_SPEECH == p.header.packet_type)
{
if (323!=ntohs(p.header.payload_length) || PKT_SPEECHD!=p.payload.audio.speechd || 160!=p.payload.audio.num_samples)
dump("Improper audio packet:", &p, packet_size(p));
buffer_depth--;
packet->SetAudioSamples(p.payload.audio.samples, true);
}
else
{
dump("ReadDevice() ERROR: Read an unexpected device packet:", &p, packet_size(p));
continue;
}
if (Encoding::dstar == type) // is this a DMR or a DStar device?
{
Controller.dstar_mux.lock();
Controller.RouteDstPacket(packet);
Controller.dstar_mux.unlock();
}
else
{
Controller.dmrst_mux.lock();
Controller.RouteDmrPacket(packet);
Controller.dmrst_mux.unlock();
}
}
}
}
void CDVDevice::AddPacket(const std::shared_ptr<CTranscoderPacket> packet) void CDVDevice::AddPacket(const std::shared_ptr<CTranscoderPacket> packet)
{ {
input_queue.push(packet); input_queue.push(packet);
} }
bool CDVDevice::SendAudio(const uint8_t channel, const int16_t *audio) const
{
// Create Audio packet based on input int8_ts
SDV_Packet p;
p.start_byte = PKT_HEADER;
const uint16_t len = 323;
p.header.payload_length = htons(len);
p.header.packet_type = PKT_SPEECH;
p.field_id = channel + PKT_CHANNEL0;
p.payload.audio.speechd = PKT_SPEECHD;
p.payload.audio.num_samples = 160U;
for (int i=0; i<160; i++)
p.payload.audio.samples[i] = htons(audio[i]);
// send audio packet to DV3000
const DWORD size = packet_size(p);
DWORD written;
auto status = FT_Write(ftHandle, &p, size, &written);
if (FT_OK != status)
{
FTDI_Error("Error writing audio packet", status);
return true;
}
else if (size != written)
{
std::cerr << "Incomplete Speech Packet write on " << description << std::endl;
return true;
}
return false;
}
bool CDVDevice::SendData(const uint8_t channel, const uint8_t *data) const
{
// Create data packet
SDV_Packet p;
p.start_byte = PKT_HEADER;
p.header.payload_length = htons(12);
p.header.packet_type = PKT_CHANNEL;
p.field_id = channel + PKT_CHANNEL0;
p.payload.ambe.chand = PKT_CHAND;
p.payload.ambe.num_bits = 72U;
memcpy(p.payload.ambe.data, data, 9);
// send data packet to DV3000
const DWORD size = packet_size(p);
DWORD written;
auto status = FT_Write(ftHandle, &p, size, &written);
if (FT_OK != status)
{
FTDI_Error("Error writing AMBE Packet", status);
return true;
}
else if (size != written)
{
std::cerr << "Incomplete AMBE Packet write on " << description << std::endl;
return true;
}
return false;
}
void CDVDevice::dump(const char *title, void *pointer, int length) const void CDVDevice::dump(const char *title, void *pointer, int length) const
{ {
const uint8_t *data = (const uint8_t *)pointer; const uint8_t *data = (const uint8_t *)pointer;

@ -24,112 +24,38 @@
#include "PacketQueue.h" #include "PacketQueue.h"
#include "ftd2xx.h" #include "ftd2xx.h"
#include "DVSIPacket.h"
#define USB3XXX_MAXPACKETSIZE 1024 // must be multiple of 64 class CDVDevice
{
#define PKT_HEADER 0x61
#define PKT_CONTROL 0x00
#define PKT_CHANNEL 0x01
#define PKT_SPEECH 0x02
#define PKT_SPEECHD 0x00
#define PKT_CHAND 0x01
#define PKT_RATET 0x09
#define PKT_INIT 0x0b
#define PKT_PRODID 0x30
#define PKT_VERSTRING 0x31
#define PKT_PARITYBYTE 0x2f
#define PKT_RESET 0x33
#define PKT_READY 0x39
#define PKT_CHANNEL0 0x40
#define PKT_CHANNEL1 0x41
#define PKT_CHANNEL2 0x42
#define PKT_PARITYMODE 0x3f
#define PKT_ECMODE 0x05
#define PKT_DCMODE 0x06
#define PKT_COMPAND 0x32
#define PKT_RATEP 0x0a
#define PKT_CHANFMT 0x15
#define PKT_SPCHFMT 0x16
#define PKT_GAIN 0x4b
#define packet_size(a) int(4 + ntohs((a).header.payload_length))
#pragma pack(push, 1)
struct dv_packet_tag {
uint8_t start_byte;
struct {
uint16_t payload_length;
uint8_t packet_type;
} header;
uint8_t field_id; // for audio and ambe, this is channel# 0x40U, 0x41U or 0x42U
union {
struct {
union {
char prodid[16];
uint8_t paritymode[3];
char version[48];
uint8_t resp[15]; // for the codec config response, ECMODE, DCMODE, RATEP, CHANFMT, SPCHFMT, GAIN and INIT
} data;
} ctrl;
struct {
uint8_t ecmode[3];
uint8_t dcmode[3];
uint8_t ratep[13];
uint8_t chfmt[3];
uint8_t spfmt[3];
uint8_t gain[3];
uint8_t init[2];
} codec;
struct {
uint8_t speechd; // 0
uint8_t num_samples; // 160
int16_t samples[160]; // 4 + 1 + 2 + 320 = 327 byte
} audio;
struct {
uint8_t chand; // 1
uint8_t num_bits; // 72 (9 bytes)
uint8_t data[9]; // 4 + 1 + 2 + 9 = 16 bytes
} ambe;
} payload;
};
#pragma pack(pop)
using SDV_Packet = struct dv_packet_tag;
enum class Encoding { dstar, dmrsf };
enum class Edvtype { dv3000, dv3003 };
class CDVDevice {
public: public:
CDVDevice(Encoding t); CDVDevice(Encoding t);
~CDVDevice(); virtual ~CDVDevice();
bool OpenDevice(const std::string &serialno, const std::string &desc, Edvtype dvtype); bool OpenDevice(const std::string &serialno, const std::string &desc, Edvtype dvtype);
void Start(); void Start();
void CloseDevice(); void CloseDevice();
std::string GetDescription() const;
void AddPacket(const std::shared_ptr<CTranscoderPacket> packet); void AddPacket(const std::shared_ptr<CTranscoderPacket> packet);
private: protected:
const Encoding type; const Encoding type;
FT_HANDLE ftHandle; FT_HANDLE ftHandle;
std::atomic<unsigned int> buffer_depth; std::atomic<unsigned int> buffer_depth;
std::atomic<bool> keep_running; std::atomic<bool> keep_running;
CPacketQueue waiting_packet[3]; // the packet currently being processed in each vocoder
CPacketQueue input_queue; CPacketQueue input_queue;
std::future<void> feedFuture, readFuture; std::future<void> feedFuture, readFuture;
std::string description; std::string description;
bool DiscoverFtdiDevices(); bool DiscoverFtdiDevices();
void FTDI_Error(const char *where, FT_STATUS status) const; void FTDI_Error(const char *where, FT_STATUS status) const;
void FeedDevice();
void ReadDevice();
bool InitDevice(); bool InitDevice();
bool ConfigureVocoder(uint8_t pkt_ch, Encoding type); bool ConfigureVocoder(uint8_t pkt_ch, Encoding type);
bool checkResponse(SDV_Packet &responsePacket, uint8_t response) const; bool checkResponse(SDV_Packet &responsePacket, uint8_t response) const;
bool SendAudio(const uint8_t channel, const int16_t *audio) const;
bool SendData(const uint8_t channel, const uint8_t *data) const;
bool GetResponse(SDV_Packet &packet); bool GetResponse(SDV_Packet &packet);
void dump(const char *title, void *data, int length) const; void dump(const char *title, void *data, int length) const;
// pure virtual methods unique to the device type
virtual void FeedDevice() = 0;
virtual void ReadDevice() = 0;
virtual bool SendAudio(const uint8_t channel, const int16_t *audio) const = 0;
virtual bool SendData(const uint8_t channel, const uint8_t *data) const = 0;
}; };

@ -0,0 +1,103 @@
#pragma once
// tcd - a hybid transcoder using DVSI hardware and Codec2 software
// Copyright © 2022 Thomas A. Early N7TAE
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <cstdint>
#define USB3XXX_MAXPACKETSIZE 1024 // must be multiple of 64
#define PKT_HEADER 0x61
#define PKT_CONTROL 0x00
#define PKT_CHANNEL 0x01
#define PKT_SPEECH 0x02
#define PKT_SPEECHD 0x00
#define PKT_CHAND 0x01
#define PKT_RATET 0x09
#define PKT_INIT 0x0b
#define PKT_PRODID 0x30
#define PKT_VERSTRING 0x31
#define PKT_PARITYBYTE 0x2f
#define PKT_RESET 0x33
#define PKT_READY 0x39
#define PKT_CHANNEL0 0x40
#define PKT_CHANNEL1 0x41
#define PKT_CHANNEL2 0x42
#define PKT_PARITYMODE 0x3f
#define PKT_ECMODE 0x05
#define PKT_DCMODE 0x06
#define PKT_COMPAND 0x32
#define PKT_RATEP 0x0a
#define PKT_CHANFMT 0x15
#define PKT_SPCHFMT 0x16
#define PKT_GAIN 0x4b
#define packet_size(a) int(4 + ntohs((a).header.payload_length))
#pragma pack(push, 1)
struct dv_packet_tag {
uint8_t start_byte;
struct {
uint16_t payload_length;
uint8_t packet_type;
} header;
uint8_t field_id; // for audio and ambe, this is channel# 0x40U, 0x41U or 0x42U
union {
struct {
union {
char prodid[16];
uint8_t paritymode[3];
char version[48];
uint8_t resp[15]; // for the codec config response, ECMODE, DCMODE, RATEP, CHANFMT, SPCHFMT, GAIN and INIT
} data;
} ctrl;
struct {
uint8_t ecmode[3];
uint8_t dcmode[3];
uint8_t ratep[13];
uint8_t chfmt[3];
uint8_t spfmt[3];
uint8_t gain[3];
uint8_t init[2];
} codec;
struct {
uint8_t speechd; // 0
uint8_t num_samples; // 160
int16_t samples[160]; // 4 + 1 + 2 + 320 = 327 byte
} audio;
struct {
uint8_t chand; // 1
uint8_t num_bits; // 72 (9 bytes)
uint8_t data[9]; // 4 + 1 + 2 + 9 = 16 bytes
} ambe;
struct {
uint8_t num_samples; // 160
int16_t samples[160]; // 4 + 1 + 2 + 320 = 327 byte
} audio3k;
struct {
uint8_t num_bits; // 72 (9 bytes)
uint8_t data[9]; // 4 + 1 + 2 + 9 = 16 bytes
} ambe3k;
} payload;
};
#pragma pack(pop)
using SDV_Packet = struct dv_packet_tag;
enum class Encoding { dstar, dmrsf };
enum class Edvtype { dv3000, dv3003 };

@ -4,7 +4,7 @@ tcd is a hybrid digital voice transcoder for ham radio used by the new URF refle
## Introduction ## Introduction
This will build a new kind of hybrid transcoder that uses AMBE DVSI-based hardware for vocoding digital voice streams used in DStar/DMR/YSF *and* David Rowe's open-source Codec2 used in M17. At a minimum, you need a USB-based DVSI device (or multiple devices) based on the DVSI-3003 or 3000 vocoder. This will build a new kind of hybrid transcoder that uses AMBE DVSI-based hardware for vocoding digital voice streams used in DStar/DMR/YSF *and* David Rowe's open-source Codec2 used in M17. TCd is optimized for performance by using a highly multi-threaded design that incorporates blocking I/O to make it as efficient as possible.
This is the only transcoder that will work with the [URF reflector](https://github.com/n7tae/urfd). This is the only transcoder that will work with the [URF reflector](https://github.com/n7tae/urfd).
@ -16,7 +16,7 @@ This software is loosely based on LX3JL's **ambed**, but is easily different eno
## Constraints and Requirements ## Constraints and Requirements
Currently, only two AMBE devices are supported. You cannot use a 3003 and a 3000 device together. If you use a pair of 3000 devices, only 460800-baud deivces are supported. When using a pair of 3000 devices, you can only transcode a single channel. If you use a pair of 3003 devices, you can specify up to three transcoded channels. AMBE devices based on LX3JL's USB-3006 open source design (sold by Northwest Digital and others) contain a pair of 3003 devices and are ideally suited for *tcd*. Currently, only two AMBE devices are supported. You cannot use a 3003 and a 3000 device together. If you use a pair of 3000 devices, only 460800-baud deivces are supported. When using a pair of 3000 devices, you can only transcode a single channel. If you use a pair of 3003 devices, you can specify up to three transcoded channels. AMBE devices based on LX3JL's [USB-3006 open source design](https://github.com/LX3JL/usb-3006) (sold by Northwest Digital and others) contain a pair of 3003 devices and are ideally suited for *tcd*.
Currently, this program must be run locally with its paired URF reflector. Remote transcoding is not yet possible. Currently, this program must be run locally with its paired URF reflector. Remote transcoding is not yet possible.

Loading…
Cancel
Save

Powered by TurnKey Linux.