support for dvsi 300{0,3}

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

@ -25,13 +25,11 @@
#include "TranscoderPacket.h"
#include "Controller.h"
CController::CController() : keep_running(true)
{
}
CController::CController() : keep_running(true) {}
bool CController::Start()
{
if (InitDevices() || reader.Open(REF2TC))
if (InitVocoders() || reader.Open(REF2TC))
{
keep_running = false;
return true;
@ -51,8 +49,10 @@ void CController::Stop()
c2Future.get();
reader.Close();
dstar_device.CloseDevice();
dmrsf_device.CloseDevice();
dstar_device->CloseDevice();
dmrsf_device->CloseDevice();
dstar_device.reset();
dmrsf_device.reset();
}
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++ )
{
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));
}
@ -122,7 +122,7 @@ bool CController::DiscoverFtdiDevices(std::list<std::pair<std::string, std::stri
return false;
}
bool CController::InitDevices()
bool CController::InitVocoders()
{
if (CheckTCModules())
return true;
@ -172,17 +172,35 @@ bool CController::InitDevices()
//initialize each device
while (! deviceset.empty())
{
if (dstar_device.OpenDevice(deviceset.front().first, deviceset.front().second, dvtype))
return true;
deviceset.pop_front();
if (dmrsf_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;
deviceset.pop_front();
if (dmrsf_device->OpenDevice(deviceset.front().first, deviceset.front().second, dvtype))
return true;
deviceset.pop_front();
}
else
{
std::cerr << "Could not create DVSI devices!" << std::endl;
return true;
deviceset.pop_front();
}
}
// and start them up!
dstar_device.Start();
dmrsf_device.Start();
dstar_device->Start();
dmrsf_device->Start();
deviceset.clear();
@ -206,10 +224,10 @@ void CController::ReadReflectorThread()
switch (packet->GetCodecIn())
{
case ECodecType::dstar:
dstar_device.AddPacket(packet);
dstar_device->AddPacket(packet);
break;
case ECodecType::dmr:
dmrsf_device.AddPacket(packet);
dmrsf_device->AddPacket(packet);
break;
case ECodecType::c2_1600:
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
dstar_device.AddPacket(packet);
dmrsf_device.AddPacket(packet);
dstar_device->AddPacket(packet);
dmrsf_device->AddPacket(packet);
}
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
codec2_queue.push(packet);
dmrsf_device.AddPacket(packet);
dmrsf_device->AddPacket(packet);
}
else if (packet->AllCodecsAreSet())
{
@ -363,7 +381,7 @@ void CController::RouteDmrPacket(std::shared_ptr<CTranscoderPacket> packet)
if (ECodecType::dmr == packet->GetCodecIn())
{
codec2_queue.push(packet);
dstar_device.AddPacket(packet);
dstar_device->AddPacket(packet);
}
else if (packet->AllCodecsAreSet())
{

@ -25,7 +25,8 @@
#include <utility>
#include "codec2.h"
#include "DVSIDevice.h"
#include "DV3000.h"
#include "DV3003.h"
#include "UnixDgramSocket.h"
#include "configure.h"
@ -49,14 +50,13 @@ protected:
CUnixDgramReader reader;
CUnixDgramWriter writer;
std::unordered_map<char, std::unique_ptr<CCodec2>> c2_16, c2_32;
CDVDevice dstar_device{Encoding::dstar};
CDVDevice dmrsf_device{Encoding::dmrsf};
std::unique_ptr<CDVDevice> dstar_device, dmrsf_device;
CPacketQueue codec2_queue;
std::mutex send_mux;
bool DiscoverFtdiDevices(std::list<std::pair<std::string, std::string>> &found);
bool InitDevices();
bool InitVocoders();
// processing threads
void ReadReflectorThread();
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;
}
std::string CDVDevice::GetDescription() const
{
return description;
}
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);
@ -232,7 +227,7 @@ bool CDVDevice::OpenDevice(const std::string &serialno, const std::string &desc,
// }
description.assign(desc);
description.append(": ");
description.append("-");
description.append(serialno);
std::cout << "Opened " << description << std::endl;
@ -512,192 +507,11 @@ bool CDVDevice::GetResponse(SDV_Packet &packet)
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)
{
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
{
const uint8_t *data = (const uint8_t *)pointer;

@ -24,112 +24,38 @@
#include "PacketQueue.h"
#include "ftd2xx.h"
#include "DVSIPacket.h"
#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;
} payload;
};
#pragma pack(pop)
using SDV_Packet = struct dv_packet_tag;
enum class Encoding { dstar, dmrsf };
enum class Edvtype { dv3000, dv3003 };
class CDVDevice {
class CDVDevice
{
public:
CDVDevice(Encoding t);
~CDVDevice();
virtual ~CDVDevice();
bool OpenDevice(const std::string &serialno, const std::string &desc, Edvtype dvtype);
void Start();
void CloseDevice();
std::string GetDescription() const;
void AddPacket(const std::shared_ptr<CTranscoderPacket> packet);
private:
protected:
const Encoding type;
FT_HANDLE ftHandle;
std::atomic<unsigned int> buffer_depth;
std::atomic<bool> keep_running;
CPacketQueue waiting_packet[3]; // the packet currently being processed in each vocoder
CPacketQueue input_queue;
std::future<void> feedFuture, readFuture;
std::string description;
bool DiscoverFtdiDevices();
void FTDI_Error(const char *where, FT_STATUS status) const;
void FeedDevice();
void ReadDevice();
bool InitDevice();
bool ConfigureVocoder(uint8_t pkt_ch, Encoding type);
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);
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
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).
@ -16,7 +16,7 @@ This software is loosely based on LX3JL's **ambed**, but is easily different eno
## 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.

Loading…
Cancel
Save

Powered by TurnKey Linux.