diff --git a/Controller.cpp b/Controller.cpp index 523afca..97a0d58 100644 --- a/Controller.cpp +++ b/Controller.cpp @@ -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::listStart(); + 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 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 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 packet) if (ECodecType::dmr == packet->GetCodecIn()) { codec2_queue.push(packet); - dstar_device.AddPacket(packet); + dstar_device->AddPacket(packet); } else if (packet->AllCodecsAreSet()) { diff --git a/Controller.h b/Controller.h index e2f1032..5436379 100644 --- a/Controller.h +++ b/Controller.h @@ -25,7 +25,8 @@ #include #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> c2_16, c2_32; - CDVDevice dstar_device{Encoding::dstar}; - CDVDevice dmrsf_device{Encoding::dmrsf}; + std::unique_ptr dstar_device, dmrsf_device; CPacketQueue codec2_queue; std::mutex send_mux; bool DiscoverFtdiDevices(std::list> &found); - bool InitDevices(); + bool InitVocoders(); // processing threads void ReadReflectorThread(); void ProcessC2Thread(); diff --git a/DV3000.cpp b/DV3000.cpp new file mode 100644 index 0000000..e065c95 --- /dev/null +++ b/DV3000.cpp @@ -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 . + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/DV3000.h b/DV3000.h new file mode 100644 index 0000000..8a0b8a9 --- /dev/null +++ b/DV3000.h @@ -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 . + + +#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 +}; diff --git a/DV3003.cpp b/DV3003.cpp new file mode 100644 index 0000000..e26faae --- /dev/null +++ b/DV3003.cpp @@ -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 . + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/DV3003.h b/DV3003.h new file mode 100644 index 0000000..b6a761f --- /dev/null +++ b/DV3003.h @@ -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 . + + +#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 +}; diff --git a/DVSIDevice.cpp b/DVSIDevice.cpp index 44b9bfd..00ffa3a 100644 --- a/DVSIDevice.cpp +++ b/DVSIDevice.cpp @@ -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 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; diff --git a/DVSIDevice.h b/DVSIDevice.h index e49e8e6..a087298 100644 --- a/DVSIDevice.h +++ b/DVSIDevice.h @@ -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 packet); -private: +protected: const Encoding type; FT_HANDLE ftHandle; std::atomic buffer_depth; std::atomic keep_running; - CPacketQueue waiting_packet[3]; // the packet currently being processed in each vocoder CPacketQueue input_queue; std::future 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; }; diff --git a/DVSIPacket.h b/DVSIPacket.h new file mode 100644 index 0000000..255a604 --- /dev/null +++ b/DVSIPacket.h @@ -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 . + +#include + +#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 }; diff --git a/README.md b/README.md index 13814e1..e55b5c9 100644 --- a/README.md +++ b/README.md @@ -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.