Add USRP2M17

This commit is contained in:
Doug McLain 2021-04-06 22:43:29 -04:00
parent d684d0924c
commit 96184ff024
42 changed files with 8332 additions and 1 deletions

View File

@ -799,7 +799,7 @@ int CP252DMR::run()
if (m_xlxReflectors != NULL)
m_xlxReflectors->clock(ms);
if (ms < 5U) CThread::sleep(5U);
if (ms < 2U) CThread::sleep(2U);
}
m_p25Network->close();

233
USRP2M17/Conf.cpp Normal file
View File

@ -0,0 +1,233 @@
/*
* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX
* Copyright (C) 2018 by Andy Uribe CA6JAU
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "Conf.h"
#include "Log.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
const int BUFFER_SIZE = 500;
enum SECTION {
SECTION_NONE,
SECTION_M17_NETWORK,
SECTION_USRP_NETWORK,
SECTION_LOG
};
CConf::CConf(const std::string& file) :
m_file(file),
m_callsign(),
m_daemon(false),
m_usrpAddress(),
m_usrpDstPort(0U),
m_usrpLocalPort(0U),
m_usrpGainAdjDb(),
m_usrpDebug(false),
m_m17Name(),
m_m17Address(),
m_m17DstPort(0U),
m_m17LocalPort(0U),
m_m17GainAdjDb(),
m_m17Debug(false),
m_logDisplayLevel(0U),
m_logFileLevel(0U),
m_logFilePath(),
m_logFileRoot()
{
}
CConf::~CConf()
{
}
bool CConf::read()
{
FILE* fp = ::fopen(m_file.c_str(), "rt");
if (fp == NULL) {
::fprintf(stderr, "Couldn't open the .ini file - %s\n", m_file.c_str());
return false;
}
SECTION section = SECTION_NONE;
char buffer[BUFFER_SIZE];
while (::fgets(buffer, BUFFER_SIZE, fp) != NULL) {
if (buffer[0U] == '#')
continue;
if (buffer[0U] == '[') {
if (::strncmp(buffer, "[M17 Network]", 13U) == 0)
section = SECTION_M17_NETWORK;
else if (::strncmp(buffer, "[USRP Network]", 14U) == 0)
section = SECTION_USRP_NETWORK;
else if (::strncmp(buffer, "[Log]", 5U) == 0)
section = SECTION_LOG;
else
section = SECTION_NONE;
continue;
}
char* key = ::strtok(buffer, " \t=\r\n");
if (key == NULL)
continue;
char* value = ::strtok(NULL, "\r\n");
if (value == NULL)
continue;
// Remove quotes from the value
size_t len = ::strlen(value);
if (len > 1U && *value == '"' && value[len - 1U] == '"') {
value[len - 1U] = '\0';
value++;
}
::fprintf(stderr, "CConf key:val:section == %s:%s:%d\n", key, value, section);
if (section == SECTION_M17_NETWORK) {
if (::strcmp(key, "Callsign") == 0)
m_callsign = value;
else if (::strcmp(key, "LocalPort") == 0)
m_m17LocalPort = (unsigned int)::atoi(value);
else if (::strcmp(key, "Name") == 0)
m_m17Name = value;
else if (::strcmp(key, "Address") == 0)
m_m17Address = value;
else if (::strcmp(key, "DstPort") == 0)
m_m17DstPort = (uint32_t)::atoi(value);
else if (::strcmp(key, "GainAdjustdB") == 0)
m_m17GainAdjDb = value;
else if (::strcmp(key, "Debug") == 0)
m_m17Debug = ::atoi(value) == 1;
} else if (section == SECTION_USRP_NETWORK) {
if (::strcmp(key, "Address") == 0)
m_usrpAddress = value;
else if (::strcmp(key, "DstPort") == 0)
m_usrpDstPort = (uint32_t)::atoi(value);
else if (::strcmp(key, "LocalPort") == 0)
m_usrpLocalPort = (uint32_t)::atoi(value);
else if (::strcmp(key, "GainAdjustdB") == 0)
m_usrpGainAdjDb = value;
else if (::strcmp(key, "Debug") == 0)
m_usrpDebug = ::atoi(value) == 1;
} else if (section == SECTION_LOG) {
if (::strcmp(key, "FilePath") == 0)
m_logFilePath = value;
else if (::strcmp(key, "FileRoot") == 0)
m_logFileRoot = value;
else if (::strcmp(key, "FileLevel") == 0)
m_logFileLevel = (uint32_t)::atoi(value);
else if (::strcmp(key, "DisplayLevel") == 0)
m_logDisplayLevel = (uint32_t)::atoi(value);
}
}
::fclose(fp);
return true;
}
std::string CConf::getCallsign() const
{
return m_callsign;
}
std::string CConf::getM17Name() const
{
return m_m17Name;
}
std::string CConf::getM17Address() const
{
return m_m17Address;
}
uint16_t CConf::getM17DstPort() const
{
return m_m17DstPort;
}
uint16_t CConf::getM17LocalPort() const
{
return m_m17LocalPort;
}
std::string CConf::getM17GainAdjDb() const
{
return m_m17GainAdjDb;
}
bool CConf::getM17Debug() const
{
return m_m17Debug;
}
bool CConf::getDaemon() const
{
return m_daemon;
}
std::string CConf::getUSRPAddress() const
{
return m_usrpAddress;
}
uint16_t CConf::getUSRPDstPort() const
{
return m_usrpDstPort;
}
uint16_t CConf::getUSRPLocalPort() const
{
return m_usrpLocalPort;
}
std::string CConf::getUSRPGainAdjDb() const
{
return m_usrpGainAdjDb;
}
bool CConf::getUSRPDebug() const
{
return m_usrpDebug;
}
uint32_t CConf::getLogDisplayLevel() const
{
return m_logDisplayLevel;
}
uint32_t CConf::getLogFileLevel() const
{
return m_logFileLevel;
}
std::string CConf::getLogFilePath() const
{
return m_logFilePath;
}
std::string CConf::getLogFileRoot() const
{
return m_logFileRoot;
}

82
USRP2M17/Conf.h Normal file
View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX
* Copyright (C) 2018 by Andy Uribe CA6JAU
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#if !defined(CONF_H)
#define CONF_H
#include <string>
#include <vector>
class CConf
{
public:
CConf(const std::string& file);
~CConf();
bool read();
// The M17 Network section
std::string getCallsign() const;
bool getDaemon() const;
std::string getM17Name() const;
std::string getM17Address() const;
uint16_t getM17DstPort() const;
uint16_t getM17LocalPort() const;
std::string getM17GainAdjDb() const;
bool getM17Debug() const;
// The USRP Network section
std::string getUSRPAddress() const;
uint16_t getUSRPDstPort() const;
uint16_t getUSRPLocalPort() const;
std::string getUSRPGainAdjDb() const;
bool getUSRPDebug() const;
// The Log section
uint32_t getLogDisplayLevel() const;
uint32_t getLogFileLevel() const;
std::string getLogFilePath() const;
std::string getLogFileRoot() const;
private:
std::string m_file;
std::string m_callsign;
bool m_daemon;
std::string m_usrpAddress;
uint16_t m_usrpDstPort;
uint16_t m_usrpLocalPort;
std::string m_usrpGainAdjDb;
bool m_usrpDebug;
std::string m_m17Name;
std::string m_m17Address;
uint16_t m_m17DstPort;
uint16_t m_m17LocalPort;
std::string m_m17GainAdjDb;
bool m_m17Debug;
uint32_t m_logDisplayLevel;
uint32_t m_logFileLevel;
std::string m_logFilePath;
std::string m_logFileRoot;
};
#endif

136
USRP2M17/Log.cpp Normal file
View File

@ -0,0 +1,136 @@
/*
* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "Log.h"
#if defined(_WIN32) || defined(_WIN64)
#include <Windows.h>
#else
#include <sys/time.h>
#endif
#include <cstdio>
#include <cstdlib>
#include <cstdarg>
#include <ctime>
#include <cassert>
#include <cstring>
static unsigned int m_fileLevel = 2U;
static std::string m_filePath;
static std::string m_fileRoot;
static FILE* m_fpLog = NULL;
static unsigned int m_displayLevel = 2U;
static struct tm m_tm;
static char LEVELS[] = " DMIWEF";
static bool LogOpen()
{
if (m_fileLevel == 0U)
return true;
time_t now;
::time(&now);
struct tm* tm = ::gmtime(&now);
if (tm->tm_mday == m_tm.tm_mday && tm->tm_mon == m_tm.tm_mon && tm->tm_year == m_tm.tm_year) {
if (m_fpLog != NULL)
return true;
} else {
if (m_fpLog != NULL)
::fclose(m_fpLog);
}
char filename[100U];
#if defined(_WIN32) || defined(_WIN64)
::sprintf(filename, "%s\\%s-%04d-%02d-%02d.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
#else
::sprintf(filename, "%s/%s-%04d-%02d-%02d.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
#endif
m_fpLog = ::fopen(filename, "a+t");
m_tm = *tm;
return m_fpLog != NULL;
}
bool LogInitialise(const std::string& filePath, const std::string& fileRoot, unsigned int fileLevel, unsigned int displayLevel)
{
m_filePath = filePath;
m_fileRoot = fileRoot;
m_fileLevel = fileLevel;
m_displayLevel = displayLevel;
return ::LogOpen();
}
void LogFinalise()
{
if (m_fpLog != NULL)
::fclose(m_fpLog);
}
void Log(unsigned int level, const char* fmt, ...)
{
assert(fmt != NULL);
char buffer[300U];
#if defined(_WIN32) || defined(_WIN64)
SYSTEMTIME st;
::GetSystemTime(&st);
::sprintf(buffer, "%c: %04u-%02u-%02u %02u:%02u:%02u.%03u ", LEVELS[level], st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
#else
struct timeval now;
::gettimeofday(&now, NULL);
struct tm* tm = ::gmtime(&now.tv_sec);
::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, now.tv_usec / 1000U);
#endif
va_list vl;
va_start(vl, fmt);
::vsprintf(buffer + ::strlen(buffer), fmt, vl);
va_end(vl);
if (level >= m_fileLevel && m_fileLevel != 0U) {
bool ret = ::LogOpen();
if (!ret)
return;
::fprintf(m_fpLog, "%s\n", buffer);
::fflush(m_fpLog);
}
if (level >= m_displayLevel && m_displayLevel != 0U) {
::fprintf(stdout, "%s\n", buffer);
::fflush(stdout);
}
if (level == 6U) { // Fatal
::fclose(m_fpLog);
exit(1);
}
}

36
USRP2M17/Log.h Normal file
View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2015,2016 by Jonathan Naylor G4KLX
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#if !defined(LOG_H)
#define LOG_H
#include <string>
#define LogDebug(fmt, ...) Log(1U, fmt, ##__VA_ARGS__)
#define LogMessage(fmt, ...) Log(2U, fmt, ##__VA_ARGS__)
#define LogInfo(fmt, ...) Log(3U, fmt, ##__VA_ARGS__)
#define LogWarning(fmt, ...) Log(4U, fmt, ##__VA_ARGS__)
#define LogError(fmt, ...) Log(5U, fmt, ##__VA_ARGS__)
#define LogFatal(fmt, ...) Log(6U, fmt, ##__VA_ARGS__)
extern void Log(unsigned int level, const char* fmt, ...);
extern bool LogInitialise(const std::string& filePath, const std::string& fileRoot, unsigned int fileLevel, unsigned int displayLevel);
extern void LogFinalise();
#endif

129
USRP2M17/M17Network.cpp Normal file
View File

@ -0,0 +1,129 @@
/*
* Copyright (C) 2009-2014,2016 by Jonathan Naylor G4KLX
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "M17Network.h"
#include "Utils.h"
#include "Log.h"
#include <cstdio>
#include <cassert>
#include <cstring>
CM17Network::CM17Network(const std::string& address, uint16_t dstPort, uint16_t localPort, uint8_t* callsign, bool debug) :
m_address(),
m_port(dstPort),
m_socket(localPort),
m_debug(debug)
{
memcpy(m_callsign, callsign, 6);
m_address = CUDPSocket::lookup(address);
}
CM17Network::~CM17Network()
{
}
bool CM17Network::open()
{
LogInfo("Opening M17 network connection");
return m_socket.open();
}
bool CM17Network::writeData(const unsigned char* data, unsigned int length)
{
assert(data != NULL);
assert(length > 0U);
if (m_debug)
CUtils::dump(1U, "M17 Network Data Sent", data, length);
return m_socket.write(data, length, m_address, m_port);
}
bool CM17Network::writePoll()
{
unsigned char data[10U];
memcpy(data, "PONG", 4);
memcpy(data+4, m_callsign, 6);
if (m_debug)
CUtils::dump(1U, "M17 Network Pong Sent", data, 10U);
return m_socket.write(data, 10U, m_address, m_port);
}
bool CM17Network::writeLink(char m)
{
unsigned char data[11U];
memcpy(data, "CONN", 4);
memcpy(data+4, m_callsign, 6);
data[10U] = m;
if (m_debug)
CUtils::dump(1U, "M17 Network Link Sent", data, 11U);
//LogInfo("writeLink add:port == %x, %x", m_address.s_addr, m_port);
return m_socket.write(data, 11U, m_address, m_port);
}
bool CM17Network::writeUnlink()
{
unsigned char data[10U];
memcpy(data, "DISC", 4);
memcpy(data+4, m_callsign, 6);
if (m_debug)
CUtils::dump(1U, "M17 Network Unlink Sent", data, 10U);
return m_socket.write(data, 10U, m_address, m_port);
}
unsigned int CM17Network::readData(unsigned char* data, unsigned int length)
{
assert(data != NULL);
assert(length > 0U);
in_addr address;
unsigned int port;
int len = m_socket.read(data, length, address, port);
if (len <= 0)
return 0U;
// Check if the data is for us
if (m_address.s_addr != address.s_addr || port != m_port) {
LogMessage("M17 packet received from an invalid source, %08X != %08X and/or %u != %u", m_address.s_addr, address.s_addr, m_port, port);
return 0U;
}
if (m_debug)
CUtils::dump(1U, "M17 Network Data Received", data, len);
return len;
}
void CM17Network::close()
{
m_socket.close();
LogInfo("Closing P25 network connection");
}

48
USRP2M17/M17Network.h Normal file
View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2009-2014,2016 by Jonathan Naylor G4KLX
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef M17Network_H
#define M17Network_H
#include "UDPSocket.h"
#include <cstdint>
#include <string>
class CM17Network {
public:
CM17Network(const std::string& address, uint16_t dstPort, uint16_t localPort, uint8_t* callsign, bool debug);
~CM17Network();
bool open();
bool writeData(const unsigned char* data, unsigned int length);
unsigned int readData(unsigned char* data, unsigned int length);
bool writePoll();
bool writeLink(char m);
bool writeUnlink();
void close();
private:
in_addr m_address;
uint16_t m_port;
CUDPSocket m_socket;
bool m_debug;
unsigned char m_callsign[6];
};
#endif

23
USRP2M17/Makefile Normal file
View File

@ -0,0 +1,23 @@
CC ?= gcc
CXX ?= g++
CFLAGS ?= -g -O3 -Wall -std=c++0x -pthread
LIBS = -lm -lpthread
LDFLAGS ?= -g
OBJECTS = Conf.o Log.o M17Network.o ModeConv.o StopWatch.o Timer.o UDPSocket.o USRPNetwork.o Utils.o \
codec2/codebooks.o codec2/kiss_fft.o codec2/lpc.o codec2/nlp.o codec2/pack.o codec2/qbase.o codec2/quantise.o codec2/codec2.o USRP2M17.o
all: USRP2M17
USRP2M17: $(OBJECTS)
$(CXX) $(OBJECTS) $(CFLAGS) $(LIBS) -o USRP2M17
%.o: %.cpp
$(CXX) $(CFLAGS) -c -o $@ $<
install:
install -m 755 USRP2M17 /usr/local/bin/
clean:
$(RM) USRP2M17 *.o *.d *.bak codec2/*.o *~

166
USRP2M17/ModeConv.cpp Normal file
View File

@ -0,0 +1,166 @@
/*
* Copyright (C) 2010,2014,2016,2018 by Jonathan Naylor G4KLX
* Copyright (C) 2016 Mathias Weyland, HB9FRV
* Copyright (C) 2018 by Andy Uribe CA6JAU
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ModeConv.h"
#include "Utils.h"
#include "Log.h"
#include <cstdio>
#include <cstring>
CModeConv::CModeConv() :
m_m17N(0U),
m_usrpN(0U),
m_M17(5000U, "USRP2M17"),
m_USRP(5000U, "M172USRP"),
m_m17GainMultiplier(1),
m_m17Attenuate(false),
m_usrpGainMultiplier(3),
m_usrpAttenuate(true)
{
m_c2 = new CCodec2(true);
}
CModeConv::~CModeConv()
{
}
void CModeConv::setUSRPGainAdjDb(std::string dbstring)
{
float db = std::stof(dbstring);
float ratio = powf(10.0, (db/10.0));
if(db < 0){
ratio = 1/ratio;
m_usrpAttenuate = true;
}
m_usrpGainMultiplier = (uint16_t)roundf(ratio);
}
void CModeConv::setM17GainAdjDb(std::string dbstring)
{
float db = std::stof(dbstring);
float ratio = powf(10.0, (db/10.0));
if(db < 0){
ratio = 1/ratio;
m_m17Attenuate = true;
}
m_m17GainMultiplier = (uint16_t)roundf(ratio);
}
void CModeConv::putUSRPHeader()
{
const uint8_t quiet[] = { 0x00u, 0x01u, 0x43u, 0x09u, 0xe4u, 0x9cu, 0x08u, 0x21u };
m_M17.addData(&TAG_HEADER, 1U);
m_M17.addData(quiet, 8U);
m_m17N += 1U;
}
void CModeConv::putUSRPEOT()
{
const uint8_t quiet[] = { 0x00u, 0x01u, 0x43u, 0x09u, 0xe4u, 0x9cu, 0x08u, 0x21u };
if((m_m17N % 2) == 0){
m_M17.addData(&TAG_DATA, 1U);
m_M17.addData(quiet, 8U);
m_m17N += 1U;
}
m_M17.addData(&TAG_EOT, 1U);
m_M17.addData(quiet, 8U);
m_m17N += 1U;
}
void CModeConv::putUSRP(int16_t* data)
{
uint8_t codec2[8U];
::memset(codec2, 0, sizeof(codec2));
int16_t audio_adjusted[160U];
for(int i = 0; i < 160; ++i){
audio_adjusted[i] = m_usrpAttenuate ? data[i] / m_usrpGainMultiplier : data[i] * m_usrpGainMultiplier;
}
m_c2->codec2_encode(codec2, audio_adjusted);
m_M17.addData(&TAG_DATA, 1U);
m_M17.addData(codec2, 8U);
m_m17N += 1U;
}
void CModeConv::putM17(uint8_t* data)
{
int16_t audio[160U];
int16_t audio_adjusted[160U];
uint8_t codec2[8U];
::memset(audio, 0, sizeof(audio));
::memcpy(codec2, &data[36], 8);
m_c2->codec2_decode(audio, codec2);
for(int i = 0; i < 160; ++i){
audio_adjusted[i] = m_m17Attenuate ? audio[i] / m_m17GainMultiplier : audio[i] * m_m17GainMultiplier;
}
m_USRP.addData(audio_adjusted, 160U);
m_usrpN += 1U;
::memcpy(codec2, &data[44], 8);
m_c2->codec2_decode(audio, codec2);
for(int i = 0; i < 160; ++i){
audio_adjusted[i] = m_m17Attenuate ? audio[i] / m_m17GainMultiplier : audio[i] * m_m17GainMultiplier;
}
m_USRP.addData(audio_adjusted, 160U);
m_usrpN += 1U;
}
bool CModeConv::getUSRP(int16_t* data)
{
if(m_usrpN){
--m_usrpN;
return m_USRP.getData(data, 160U);
}
else{
return false;
}
}
uint32_t CModeConv::getM17(uint8_t* data)
{
uint8_t tag[2U];
tag[0U] = TAG_NODATA;
tag[1U] = TAG_NODATA;
if (m_m17N >= 2U) {
m_M17.getData(tag, 1U);
m_M17.getData(data, 8U);
m_M17.getData(tag+1, 1U);
m_M17.getData(data+8, 8U);
m_m17N -= 2U;
}
return (tag[1U] == TAG_EOT) ? tag[1U] : tag[0];
}

58
USRP2M17/ModeConv.h Normal file
View File

@ -0,0 +1,58 @@
/*
* Copyright (C) 2010,2014,2016,2018 by Jonathan Naylor G4KLX
* Copyright (C) 2018 by Andy Uribe CA6JAU
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "RingBuffer.h"
#include "codec2/codec2.h"
const unsigned char TAG_HEADER = 0x00U;
const unsigned char TAG_DATA = 0x01U;
const unsigned char TAG_LOST = 0x02U;
const unsigned char TAG_EOT = 0x03U;
const unsigned char TAG_NODATA = 0x04U;
#if !defined(MODECONV_H)
#define MODECONV_H
class CModeConv {
public:
CModeConv();
~CModeConv();
void setUSRPGainAdjDb(std::string dbstring);
void setM17GainAdjDb(std::string dbstring);
void putUSRP(int16_t* data);
void putUSRPHeader();
void putUSRPEOT();
void putM17(uint8_t* data);
uint32_t getM17(uint8_t* data);
bool getUSRP(int16_t* data);
private:
uint32_t m_m17N;
uint32_t m_usrpN;
CRingBuffer<uint8_t> m_M17;
CRingBuffer<int16_t> m_USRP;
CCodec2 *m_c2;
uint16_t m_m17GainMultiplier;
bool m_m17Attenuate;
uint16_t m_usrpGainMultiplier;
bool m_usrpAttenuate;
};
#endif

9
USRP2M17/README.md Normal file
View File

@ -0,0 +1,9 @@
# Description
This is the source code of USRP2M17, which converts USRP PCM audio and M17 digital mode, based on Jonathan G4KLX's [MMDVM](https://github.com/g4klx) software. Typical uses are connecting M17 reflectors to AllStar nodes and can be used with MMDVM modems in FM mode as stand alone radios.
# Configuration
M17 and USRP both have a configuration value 'GainAdjustDB". Thru trial and error I have found the best balance of audio levels which are set as the defaults in the provided USRP2M17.ini file. For AllStar connections, USRP address and ports are the values defined in your USRP channel based node.
# Building
run 'make' from the source directory.

154
USRP2M17/RingBuffer.h Normal file
View File

@ -0,0 +1,154 @@
/*
* Copyright (C) 2006-2009,2012,2013,2015,2016 by Jonathan Naylor G4KLX
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef RingBuffer_H
#define RingBuffer_H
#include "Log.h"
#include <cstdio>
#include <cassert>
#include <cstring>
template<class T> class CRingBuffer {
public:
CRingBuffer(unsigned int length, const char* name) :
m_length(length),
m_name(name),
m_buffer(NULL),
m_iPtr(0U),
m_oPtr(0U)
{
assert(length > 0U);
assert(name != NULL);
m_buffer = new T[length];
::memset(m_buffer, 0x00, m_length * sizeof(T));
}
~CRingBuffer()
{
delete[] m_buffer;
}
bool addData(const T* buffer, unsigned int nSamples)
{
if (nSamples >= freeSpace()) {
LogError("%s buffer overflow, clearing the buffer. (%u >= %u)", m_name, nSamples, freeSpace());
clear();
return false;
}
for (unsigned int i = 0U; i < nSamples; i++) {
m_buffer[m_iPtr++] = buffer[i];
if (m_iPtr == m_length)
m_iPtr = 0U;
}
return true;
}
bool getData(T* buffer, unsigned int nSamples)
{
if (dataSize() < nSamples) {
LogError("**** Underflow in %s ring buffer, %u < %u", m_name, dataSize(), nSamples);
return false;
}
for (unsigned int i = 0U; i < nSamples; i++) {
buffer[i] = m_buffer[m_oPtr++];
if (m_oPtr == m_length)
m_oPtr = 0U;
}
return true;
}
bool peek(T* buffer, unsigned int nSamples)
{
if (dataSize() < nSamples) {
LogError("**** Underflow peek in %s ring buffer, %u < %u", m_name, dataSize(), nSamples);
return false;
}
unsigned int ptr = m_oPtr;
for (unsigned int i = 0U; i < nSamples; i++) {
buffer[i] = m_buffer[ptr++];
if (ptr == m_length)
ptr = 0U;
}
return true;
}
void clear()
{
m_iPtr = 0U;
m_oPtr = 0U;
::memset(m_buffer, 0x00, m_length * sizeof(T));
}
unsigned int freeSpace() const
{
unsigned int len = m_length;
if (m_oPtr > m_iPtr)
len = m_oPtr - m_iPtr;
else if (m_iPtr > m_oPtr)
len = m_length - (m_iPtr - m_oPtr);
if (len > m_length)
len = 0U;
return len;
}
unsigned int dataSize() const
{
return m_length - freeSpace();
}
bool hasSpace(unsigned int length) const
{
return freeSpace() > length;
}
bool hasData() const
{
return m_oPtr != m_iPtr;
}
bool isEmpty() const
{
return m_oPtr == m_iPtr;
}
private:
unsigned int m_length;
const char* m_name;
T* m_buffer;
unsigned int m_iPtr;
unsigned int m_oPtr;
};
#endif

105
USRP2M17/StopWatch.cpp Normal file
View File

@ -0,0 +1,105 @@
/*
* Copyright (C) 2015,2016,2018 by Jonathan Naylor G4KLX
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "StopWatch.h"
#if defined(_WIN32) || defined(_WIN64)
CStopWatch::CStopWatch() :
m_frequencyS(),
m_frequencyMS(),
m_start()
{
::QueryPerformanceFrequency(&m_frequencyS);
m_frequencyMS.QuadPart = m_frequencyS.QuadPart / 1000ULL;
}
CStopWatch::~CStopWatch()
{
}
unsigned long long CStopWatch::time() const
{
LARGE_INTEGER now;
::QueryPerformanceCounter(&now);
return (unsigned long long)(now.QuadPart / m_frequencyMS.QuadPart);
}
unsigned long long CStopWatch::start()
{
::QueryPerformanceCounter(&m_start);
return (unsigned long long)(m_start.QuadPart / m_frequencyS.QuadPart);
}
unsigned int CStopWatch::elapsed()
{
LARGE_INTEGER now;
::QueryPerformanceCounter(&now);
LARGE_INTEGER temp;
temp.QuadPart = (now.QuadPart - m_start.QuadPart) * 1000;
return (unsigned int)(temp.QuadPart / m_frequencyS.QuadPart);
}
#else
#include <cstdio>
#include <ctime>
CStopWatch::CStopWatch() :
m_startMS(0ULL)
{
}
CStopWatch::~CStopWatch()
{
}
unsigned long long CStopWatch::time() const
{
struct timeval now;
::gettimeofday(&now, NULL);
return now.tv_sec * 1000ULL + now.tv_usec / 1000ULL;
}
unsigned long long CStopWatch::start()
{
struct timespec now;
::clock_gettime(CLOCK_MONOTONIC, &now);
m_startMS = now.tv_sec * 1000ULL + now.tv_nsec / 1000000ULL;
return m_startMS;
}
unsigned int CStopWatch::elapsed()
{
struct timespec now;
::clock_gettime(CLOCK_MONOTONIC, &now);
unsigned long long nowMS = now.tv_sec * 1000ULL + now.tv_nsec / 1000000ULL;
return nowMS - m_startMS;
}
#endif

49
USRP2M17/StopWatch.h Normal file
View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2015,2016,2018 by Jonathan Naylor G4KLX
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#if !defined(STOPWATCH_H)
#define STOPWATCH_H
#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#else
#include <sys/time.h>
#endif
class CStopWatch
{
public:
CStopWatch();
~CStopWatch();
unsigned long long time() const;
unsigned long long start();
unsigned int elapsed();
private:
#if defined(_WIN32) || defined(_WIN64)
LARGE_INTEGER m_frequencyS;
LARGE_INTEGER m_frequencyMS;
LARGE_INTEGER m_start;
#else
unsigned long long m_startMS;
#endif
};
#endif

68
USRP2M17/Timer.cpp Normal file
View File

@ -0,0 +1,68 @@
/*
* Copyright (C) 2009,2010,2015 by Jonathan Naylor G4KLX
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "Timer.h"
#include <cstdio>
#include <cassert>
CTimer::CTimer(unsigned int ticksPerSec, unsigned int secs, unsigned int msecs) :
m_ticksPerSec(ticksPerSec),
m_timeout(0U),
m_timer(0U)
{
assert(ticksPerSec > 0U);
if (secs > 0U || msecs > 0U) {
// m_timeout = ((secs * 1000U + msecs) * m_ticksPerSec) / 1000U + 1U;
unsigned long long temp = (secs * 1000ULL + msecs) * m_ticksPerSec;
m_timeout = (unsigned int)(temp / 1000ULL + 1ULL);
}
}
CTimer::~CTimer()
{
}
void CTimer::setTimeout(unsigned int secs, unsigned int msecs)
{
if (secs > 0U || msecs > 0U) {
// m_timeout = ((secs * 1000U + msecs) * m_ticksPerSec) / 1000U + 1U;
unsigned long long temp = (secs * 1000ULL + msecs) * m_ticksPerSec;
m_timeout = (unsigned int)(temp / 1000ULL + 1ULL);
} else {
m_timeout = 0U;
m_timer = 0U;
}
}
unsigned int CTimer::getTimeout() const
{
if (m_timeout == 0U)
return 0U;
return (m_timeout - 1U) / m_ticksPerSec;
}
unsigned int CTimer::getTimer() const
{
if (m_timer == 0U)
return 0U;
return (m_timer - 1U) / m_ticksPerSec;
}

89
USRP2M17/Timer.h Normal file
View File

@ -0,0 +1,89 @@
/*
* Copyright (C) 2009,2010,2011,2014 by Jonathan Naylor G4KLX
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef Timer_H
#define Timer_H
class CTimer {
public:
CTimer(unsigned int ticksPerSec, unsigned int secs = 0U, unsigned int msecs = 0U);
~CTimer();
void setTimeout(unsigned int secs, unsigned int msecs = 0U);
unsigned int getTimeout() const;
unsigned int getTimer() const;
unsigned int getRemaining()
{
if (m_timeout == 0U || m_timer == 0U)
return 0U;
if (m_timer >= m_timeout)
return 0U;
return (m_timeout - m_timer) / m_ticksPerSec;
}
bool isRunning()
{
return m_timer > 0U;
}
void start(unsigned int secs, unsigned int msecs = 0U)
{
setTimeout(secs, msecs);
start();
}
void start()
{
if (m_timeout > 0U)
m_timer = 1U;
}
void stop()
{
m_timer = 0U;
}
bool hasExpired()
{
if (m_timeout == 0U || m_timer == 0U)
return false;
if (m_timer >= m_timeout)
return true;
return false;
}
void clock(unsigned int ticks = 1U)
{
if (m_timer > 0U && m_timeout > 0U)
m_timer += ticks;
}
private:
unsigned int m_ticksPerSec;
unsigned int m_timeout;
unsigned int m_timer;
};
#endif

212
USRP2M17/UDPSocket.cpp Normal file
View File

@ -0,0 +1,212 @@
/*
* Copyright (C) 2006-2016 by Jonathan Naylor G4KLX
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "UDPSocket.h"
#include "Log.h"
#include <cassert>
#include <cerrno>
#include <cstring>
CUDPSocket::CUDPSocket(const std::string& address, unsigned int port) :
m_address(address),
m_port(port),
m_fd(-1)
{
assert(!address.empty());
}
CUDPSocket::CUDPSocket(unsigned int port) :
m_address(),
m_port(port),
m_fd(-1)
{
}
CUDPSocket::~CUDPSocket()
{
}
in_addr CUDPSocket::lookup(const std::string& hostname)
{
in_addr addr;
in_addr_t address = ::inet_addr(hostname.c_str());
if (address != in_addr_t(-1)) {
addr.s_addr = address;
LogInfo("inet_addr() returns %x", addr.s_addr);
return addr;
}
struct hostent* hp = ::gethostbyname(hostname.c_str());
if (hp != NULL) {
::memcpy(&addr, hp->h_addr_list[0], sizeof(struct in_addr));
LogInfo("gethostbyname() returns %x", addr.s_addr);
return addr;
}
LogError("Cannot find address for host %s", hostname.c_str());
addr.s_addr = INADDR_NONE;
return addr;
}
bool CUDPSocket::open()
{
m_fd = ::socket(AF_INET, SOCK_DGRAM, 0);
if (m_fd < 0) {
LogError("Cannot create the UDP socket, err: %d", errno);
return false;
}
if (m_port > 0U) {
sockaddr_in addr;
::memset(&addr, 0x00, sizeof(sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(m_port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (!m_address.empty()) {
addr.sin_addr.s_addr = ::inet_addr(m_address.c_str());
if (addr.sin_addr.s_addr == INADDR_NONE) {
LogError("The local address is invalid - %s", m_address.c_str());
return false;
}
}
int reuse = 1;
if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) {
LogError("Cannot set the UDP socket option, err: %d", errno);
return false;
}
if (::bind(m_fd, (sockaddr*)&addr, sizeof(sockaddr_in)) == -1) {
LogError("Cannot bind the UDP address, err: %d", errno);
return false;
}
}
return true;
}
int CUDPSocket::read(unsigned char* buffer, unsigned int length, in_addr& address, unsigned int& port)
{
assert(buffer != NULL);
assert(length > 0U);
// Check that the readfrom() won't block
fd_set readFds;
FD_ZERO(&readFds);
#if defined(_WIN32) || defined(_WIN64)
FD_SET((unsigned int)m_fd, &readFds);
#else
FD_SET(m_fd, &readFds);
#endif
// Return immediately
timeval tv;
tv.tv_sec = 0L;
tv.tv_usec = 0L;
int ret = ::select(m_fd + 1, &readFds, NULL, NULL, &tv);
if (ret < 0) {
#if defined(_WIN32) || defined(_WIN64)
LogError("Error returned from UDP select, err: %lu", ::GetLastError());
#else
LogError("Error returned from UDP select, err: %d", errno);
#endif
return -1;
}
if (ret == 0)
return 0;
sockaddr_in addr;
#if defined(_WIN32) || defined(_WIN64)
int size = sizeof(sockaddr_in);
#else
socklen_t size = sizeof(sockaddr_in);
#endif
#if defined(_WIN32) || defined(_WIN64)
int len = ::recvfrom(m_fd, (char*)buffer, length, 0, (sockaddr *)&addr, &size);
#else
ssize_t len = ::recvfrom(m_fd, (char*)buffer, length, 0, (sockaddr *)&addr, &size);
#endif
if (len <= 0) {
#if defined(_WIN32) || defined(_WIN64)
LogError("Error returned from recvfrom, err: %lu", ::GetLastError());
#else
LogError("Error returned from recvfrom, err: %d", errno);
#endif
return -1;
}
address = addr.sin_addr;
port = ntohs(addr.sin_port);
return len;
}
bool CUDPSocket::write(const unsigned char* buffer, unsigned int length, const in_addr& address, unsigned int port)
{
assert(buffer != NULL);
assert(length > 0U);
sockaddr_in addr;
::memset(&addr, 0x00, sizeof(sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_addr = address;
addr.sin_port = htons(port);
ssize_t ret = ::sendto(m_fd, (char *)buffer, length, 0, (sockaddr *)&addr, sizeof(sockaddr_in));
if (ret < 0) {
LogError("Error returned from sendto, err: %d", errno);
return false;
}
if (ret != ssize_t(length))
return false;
return true;
}
void CUDPSocket::close()
{
#if defined(_WIN32) || defined(_WIN64)
::closesocket(m_fd);
#else
::close(m_fd);
#endif
}

54
USRP2M17/UDPSocket.h Normal file
View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2009-2011,2013,2015,2016 by Jonathan Naylor G4KLX
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef UDPSocket_H
#define UDPSocket_H
#include <string>
#include <netdb.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
class CUDPSocket {
public:
CUDPSocket(const std::string& address, unsigned int port = 0U);
CUDPSocket(unsigned int port = 0U);
~CUDPSocket();
bool open();
int read(unsigned char* buffer, unsigned int length, in_addr& address, unsigned int& port);
bool write(const unsigned char* buffer, unsigned int length, const in_addr& address, unsigned int port);
void close();
static in_addr lookup(const std::string& hostName);
private:
std::string m_address;
unsigned short m_port;
int m_fd;
};
#endif

438
USRP2M17/USRP2M17.cpp Normal file
View File

@ -0,0 +1,438 @@
/*
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "USRP2M17.h"
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <pwd.h>
#define USRP_FRAME_PER 15U
#define M17_FRAME_PER 35U
const char* DEFAULT_INI_FILE = "/etc/USRP2M17.ini";
const char* HEADER1 = "This software is for use on amateur radio networks only,";
const char* HEADER2 = "it is to be used for educational purposes only. Its use on";
const char* HEADER3 = "commercial networks is strictly prohibited.";
const char* HEADER4 = "Copyright(C) 2021 by AD8DP";
#define M17CHARACTERS " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/."
#include <functional>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <clocale>
#include <cctype>
static bool m_killed = false;
void sig_handler(int signo)
{
if (signo == SIGTERM) {
m_killed = true;
::fprintf(stdout, "Received SIGTERM\n");
}
}
void encode_callsign(uint8_t *callsign)
{
const std::string m17_alphabet(M17CHARACTERS);
char cs[10];
memset(cs, 0, sizeof(cs));
memcpy(cs, callsign, strlen((char *)callsign));
uint64_t encoded = 0;
for(int i = std::strlen((char *)callsign)-1; i >= 0; i--) {
auto pos = m17_alphabet.find(cs[i]);
if (pos == std::string::npos) {
pos = 0;
}
encoded *= 40;
encoded += pos;
}
for (int i=0; i<6; i++) {
callsign[i] = (encoded >> (8*(5-i)) & 0xFFU);
}
}
void decode_callsign(uint8_t *callsign)
{
const std::string m17_alphabet(M17CHARACTERS);
uint8_t code[6];
uint64_t coded = callsign[0];
for (int i=1; i<6; i++)
coded = (coded << 8) | callsign[i];
if (coded > 0xee6b27ffffffu) {
//std::cerr << "Callsign code is too large, 0x" << std::hex << coded << std::endl;
return;
}
memcpy(code, callsign, 6);
memset(callsign, 0, 10);
int i = 0;
while (coded) {
callsign[i++] = m17_alphabet[coded % 40];
coded /= 40;
}
}
int main(int argc, char** argv)
{
const char* iniFile = DEFAULT_INI_FILE;
if (argc > 1) {
for (int currentArg = 1; currentArg < argc; ++currentArg) {
std::string arg = argv[currentArg];
if ((arg == "-v") || (arg == "--version")) {
::fprintf(stdout, "USRP2M17 version %s\n", VERSION);
return 0;
} else if (arg.substr(0, 1) == "-") {
::fprintf(stderr, "Usage: USRP2M17 [-v|--version] [filename]\n");
return 1;
} else {
iniFile = argv[currentArg];
}
}
}
// Capture SIGTERM to finish gracelessly
if (signal(SIGTERM, sig_handler) == SIG_ERR)
::fprintf(stdout, "Can't catch SIGTERM\n");
CUSRP2M17* gateway = new CUSRP2M17(std::string(iniFile));
int ret = gateway->run();
delete gateway;
return ret;
}
CUSRP2M17::CUSRP2M17(const std::string& configFile) :
m_callsign(),
m_m17Ref(),
m_conf(configFile),
m_usrpNetwork(NULL),
m_m17Network(NULL),
m_conv(),
m_m17Src(),
m_m17Dst(),
m_m17Frame(NULL),
m_m17Frames(0U),
m_usrpFrame(NULL),
m_usrpFrames(0U)
{
m_m17Frame = new uint8_t[100U];
m_usrpFrame = new uint8_t[400U];
::memset(m_m17Frame, 0U, 100U);
::memset(m_usrpFrame, 0U, 400U);
}
CUSRP2M17::~CUSRP2M17()
{
delete[] m_m17Frame;
delete[] m_usrpFrame;
}
int CUSRP2M17::run()
{
bool ret = m_conf.read();
if (!ret) {
::fprintf(stderr, "USRP2M17: cannot read the .ini file\n");
return 1;
}
//setlocale(LC_ALL, "C");
uint32_t logDisplayLevel = m_conf.getLogDisplayLevel();
if(m_conf.getDaemon())
logDisplayLevel = 0U;
bool m_daemon = m_conf.getDaemon();
if (m_daemon) {
// Create new process
pid_t pid = ::fork();
if (pid == -1) {
::fprintf(stderr, "Couldn't fork() , exiting\n");
return -1;
} else if (pid != 0)
exit(EXIT_SUCCESS);
// Create new session and process group
if (::setsid() == -1) {
::fprintf(stderr, "Couldn't setsid(), exiting\n");
return -1;
}
// Set the working directory to the root directory
if (::chdir("/") == -1) {
::fprintf(stderr, "Couldn't cd /, exiting\n");
return -1;
}
// If we are currently root...
if (getuid() == 0) {
struct passwd* user = ::getpwnam("mmdvm");
if (user == NULL) {
::fprintf(stderr, "Could not get the mmdvm user, exiting\n");
return -1;
}
uid_t mmdvm_uid = user->pw_uid;
gid_t mmdvm_gid = user->pw_gid;
// Set user and group ID's to mmdvm:mmdvm
if (setgid(mmdvm_gid) != 0) {
::fprintf(stderr, "Could not set mmdvm GID, exiting\n");
return -1;
}
if (setuid(mmdvm_uid) != 0) {
::fprintf(stderr, "Could not set mmdvm UID, exiting\n");
return -1;
}
// Double check it worked (AKA Paranoia)
if (setuid(0) != -1) {
::fprintf(stderr, "It's possible to regain root - something is wrong!, exiting\n");
return -1;
}
}
}
ret = ::LogInitialise(m_conf.getLogFilePath(), m_conf.getLogFileRoot(), m_conf.getLogFileLevel(), logDisplayLevel);
if (!ret) {
::fprintf(stderr, "USRP2M17: unable to open the log file\n");
return 1;
}
if (m_daemon) {
::close(STDIN_FILENO);
::close(STDOUT_FILENO);
::close(STDERR_FILENO);
}
LogInfo(HEADER1);
LogInfo(HEADER2);
LogInfo(HEADER3);
LogInfo(HEADER4);
m_callsign = m_conf.getCallsign();
m_m17Ref = m_conf.getM17Name();
char module = m_m17Ref.c_str()[m_m17Ref.find(' ')+1];
std::string m17_address = m_conf.getM17Address();
uint16_t m17_dstPort = m_conf.getM17DstPort();
uint16_t m17_localPort = m_conf.getM17LocalPort();
bool m17_debug = m_conf.getM17Debug();
m_conv.setM17GainAdjDb(m_conf.getM17GainAdjDb());
uint16_t streamid = 0;
uint8_t m17_src[10];
uint8_t m17_dst[10];
memcpy(m17_src, m_callsign.c_str(), 9);
m17_src[9] = 0x00;
encode_callsign(m17_src);
m_m17Network = new CM17Network(m17_address, m17_dstPort, m17_localPort, m17_src, m17_debug);
ret = m_m17Network->open();
if (!ret) {
::LogError("Cannot open the M17 network port");
::LogFinalise();
return 1;
}
std::string usrp_address = m_conf.getUSRPAddress();
uint16_t usrp_dstPort = m_conf.getUSRPDstPort();
uint16_t usrp_localPort = m_conf.getUSRPLocalPort();
bool usrp_debug = m_conf.getUSRPDebug();
m_conv.setUSRPGainAdjDb(m_conf.getUSRPGainAdjDb());
m_usrpNetwork = new CUSRPNetwork(usrp_address, usrp_dstPort, usrp_localPort, usrp_debug);
ret = m_usrpNetwork->open();
if (!ret) {
::LogError("Cannot open the USRP network port");
::LogFinalise();
return 1;
}
CTimer networkWatchdog(100U, 0U, 1500U);
CTimer pollTimer(1000U, 8U);
CStopWatch stopWatch;
CStopWatch m17Watch;
CStopWatch usrpWatch;
pollTimer.start();
stopWatch.start();
m17Watch.start();
usrpWatch.start();
uint16_t m17_cnt = 0;
uint32_t usrp_cnt = 0;
m_m17Network->writeLink(module);
LogMessage("Starting USRP2M17-%s", VERSION);
for (; m_killed == 0;) {
uint8_t buffer[2000U];
memset(buffer, 0, sizeof(buffer));
uint32_t ms = stopWatch.elapsed();
if (m17Watch.elapsed() > M17_FRAME_PER) {
uint32_t m17FrameType = m_conv.getM17(m_m17Frame);
if(m17FrameType == TAG_HEADER) {
m17_cnt = 0U;
m17Watch.start();
streamid = static_cast<uint16_t>((::rand() & 0xFFFF));
memcpy(m17_dst, m_m17Ref.c_str(), m_m17Ref.size());
m17_dst[9] = 0x00;
encode_callsign(m17_dst);
memcpy(buffer, "M17 ", 4);
memcpy(buffer+4, &streamid, 2);
memcpy(buffer+6, m17_dst, 6);
memcpy(buffer+12, m17_src, 6);
buffer[19] = 0x05;
memcpy(buffer+36, m_m17Frame, 16);
m_m17Network->writeData(buffer, 54U);
}
else if(m17FrameType == TAG_EOT) {
m17_cnt |= 0x8000;
memcpy(buffer, "M17 ", 4);
memcpy(buffer+4, &streamid, 2);
memcpy(buffer+6, m17_dst, 6);
memcpy(buffer+12, m17_src, 6);
buffer[19] = 0x05;
buffer[34] = m17_cnt >> 8;
buffer[35] = m17_cnt & 0xff;
memcpy(buffer+36, m_m17Frame, 16);
m_m17Network->writeData(buffer, 54U);
m17Watch.start();
}
else if(m17FrameType == TAG_DATA) {
//CUtils::dump(1U, "M17 Data", m_p25Frame, 11U);
m17_cnt++;
memcpy(buffer, "M17 ", 4);
memcpy(buffer+4, &streamid, 2);
memcpy(buffer+6, m17_dst, 6);
memcpy(buffer+12, m17_src, 6);
buffer[19] = 0x05;
buffer[34] = m17_cnt >> 8;
buffer[35] = m17_cnt & 0xff;
memcpy(buffer+36, m_m17Frame, 16);
m_m17Network->writeData(buffer, 54U);
m17Watch.start();
}
}
while (m_m17Network->readData(m_m17Frame, 54U) > 0U) {
//CUtils::dump(1U, "M17 Data", m_p25Frame, 22U);
if (!memcmp(m_m17Frame, "M17 ", 4)) {
if (m_m17Frame[34] == 0 && m_m17Frame[35] == 0) {
m_m17Frames = 0;
}
else if (m_m17Frame[34U] & 0x80U) {
LogMessage("M17 received end of voice transmission, %.1f seconds", float(m_m17Frames) / 25.0F);
}
else{
m_conv.putM17(m_m17Frame);
}
uint8_t cs[10];
memcpy(cs, m_m17Frame+12, 6);
decode_callsign(cs);
std::string css((char *)cs);
css = css.substr(0, css.find(' '));
m_m17Frames++;
}
}
if (usrpWatch.elapsed() > USRP_FRAME_PER) {
int16_t pcm[160];
if(m_conv.getUSRP(pcm)){
//CUtils::dump(1U, "USRP data:", m_usrpFrame, 33U);
const uint32_t cnt = htonl(usrp_cnt);
memcpy(m_usrpFrame, "USRP", 4);
memset(m_usrpFrame+4, 0, 28);
memcpy(m_usrpFrame+4, &cnt, 4);
m_usrpFrame[15] = 1;
for(int i = 0; i < 320; i+=2){
m_usrpFrame[32+i] = pcm[(i/2)] & 0xff;
m_usrpFrame[32+i+1] = pcm[(i/2)] >> 8;
}
m_usrpNetwork->writeData(m_usrpFrame, 352);
usrp_cnt++;
usrpWatch.start();
}
}
uint32_t len = 0;
while ( (len = m_usrpNetwork->readData(m_usrpFrame, 400)) ) {
if((len == 32) && !memcmp(m_usrpFrame, "USRP", 4)) {
LogMessage("USRP received end of voice transmission, %.1f seconds", float(m_usrpFrames) / 50.0F);
m_conv.putUSRPEOT();
m_usrpFrames = 0U;
}
if( (!memcmp(m_usrpFrame, "USRP", 4)) && len == 352) {
if(!m_usrpFrames){
m_conv.putUSRPHeader();
LogMessage("USRP first frame received");
}
int16_t pcm[160];
for(int i = 0; i < 160; ++i){
pcm[i] = (m_usrpFrame[32+(i*2)+1] << 8) | m_usrpFrame[32+(i*2)];
}
m_conv.putUSRP(pcm);
m_usrpFrames++;
}
}
stopWatch.start();
pollTimer.clock(ms);
if (pollTimer.isRunning() && pollTimer.hasExpired()) {
m_m17Network->writePoll();
pollTimer.start();
}
if (ms < 5U) ::usleep(5U * 1000U);
}
m_m17Network->close();
m_usrpNetwork->close();
delete m_usrpNetwork;
delete m_m17Network;
::LogFinalise();
return 0;
}

58
USRP2M17/USRP2M17.h Normal file
View File

@ -0,0 +1,58 @@
/*
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#if !defined(USRP2M17_H)
#define USRP2M17_H
#include "ModeConv.h"
#include "USRPNetwork.h"
#include "M17Network.h"
#include "UDPSocket.h"
#include "StopWatch.h"
#include "Version.h"
#include "Timer.h"
#include "Utils.h"
#include "Conf.h"
#include "Log.h"
#include <string>
class CUSRP2M17
{
public:
CUSRP2M17(const std::string& configFile);
~CUSRP2M17();
int run();
private:
std::string m_callsign;
std::string m_m17Ref;
CConf m_conf;
CUSRPNetwork* m_usrpNetwork;
CM17Network* m_m17Network;
CModeConv m_conv;
std::string m_m17Src;
std::string m_m17Dst;
uint8_t* m_m17Frame;
uint32_t m_m17Frames;
uint8_t* m_usrpFrame;
uint32_t m_usrpFrames;
};
#endif

24
USRP2M17/USRP2M17.ini Normal file
View File

@ -0,0 +1,24 @@
[M17 Network]
Callsign=AD8DP D
Address=51.81.119.111
Name=M17-M17 C
LocalPort=32010
DstPort=17000
GainAdjustdB=3
Daemon=0
Debug=1
[USRP Network]
Address=127.0.0.1
DstPort=32001
LocalPort=34001
GainAdjustdB=-6
Debug=1
[Log]
# Logging levels, 0=No logging
DisplayLevel=1
FileLevel=1
FilePath=.
FileRoot=USRP2M17

78
USRP2M17/USRPNetwork.cpp Normal file
View File

@ -0,0 +1,78 @@
/*
* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "USRPNetwork.h"
#include "StopWatch.h"
#include "Utils.h"
#include "Log.h"
#include <cstdio>
CUSRPNetwork::CUSRPNetwork(const std::string& address, uint16_t dstPort, uint16_t localPort, bool debug) :
m_address(),
m_port(dstPort),
m_socket(localPort),
m_debug(debug)
{
m_address = CUDPSocket::lookup(address);
CStopWatch stopWatch;
}
CUSRPNetwork::~CUSRPNetwork()
{
}
bool CUSRPNetwork::open()
{
LogMessage("USRP Network, Opening");
return m_socket.open();
}
void CUSRPNetwork::close()
{
m_socket.close();
}
uint32_t CUSRPNetwork::readData(uint8_t* data, uint32_t length)
{
in_addr address;
unsigned int port;
int len = m_socket.read(data, length, address, port);
if (len <= 0)
return 0U;
// Check if the data is for us
if (m_address.s_addr != address.s_addr || port != m_port) {
LogMessage("USRP packet received from an invalid source, %08X != %08X and/or %u != %u", m_address.s_addr, address.s_addr, m_port, port);
return 0U;
}
if (m_debug)
CUtils::dump(1U, "USRP Network Data Received", data, len);
return len;
}
bool CUSRPNetwork::writeData(const uint8_t* data, uint32_t length)
{
if (m_debug)
CUtils::dump(1U, "USRP Network Data Sent", data, length);
return m_socket.write(data, length, m_address, m_port);
}

47
USRP2M17/USRPNetwork.h Normal file
View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX
* Copyright (C) 2021 by Doug McLain AD8DP
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#if !defined(USRPNetwork_H)
#define USRPNetwork_H
#include "UDPSocket.h"
#include "Timer.h"
#include <string>
#include <cstdint>
class CUSRPNetwork
{
public:
CUSRPNetwork(const std::string& address, uint16_t dstPort, uint16_t localPort, bool debug);
~CUSRPNetwork();
uint32_t getConfig(uint8_t* config) const;
bool open();
bool writeData(const uint8_t* data, uint32_t length);
uint32_t readData(uint8_t* data, uint32_t length);
void close();
private:
in_addr m_address;
uint16_t m_port;
CUDPSocket m_socket;
bool m_debug;
};
#endif

146
USRP2M17/Utils.cpp Normal file
View File

@ -0,0 +1,146 @@
/*
* Copyright (C) 2009,2014,2015,2016 Jonathan Naylor, G4KLX
*
* 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; version 2 of the License.
*
* 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.
*/
#include "Utils.h"
#include "Log.h"
#include <cstdio>
#include <cassert>
void CUtils::dump(const std::string& title, const unsigned char* data, unsigned int length)
{
assert(data != NULL);
dump(2U, title, data, length);
}
void CUtils::dump(int level, const std::string& title, const unsigned char* data, unsigned int length)
{
assert(data != NULL);
::Log(level, "%s", title.c_str());
unsigned int offset = 0U;
while (length > 0U) {
std::string output;
unsigned int bytes = (length > 16U) ? 16U : length;
for (unsigned i = 0U; i < bytes; i++) {
char temp[10U];
::sprintf(temp, "%02X ", data[offset + i]);
output += temp;
}
for (unsigned int i = bytes; i < 16U; i++)
output += " ";
output += " *";
for (unsigned i = 0U; i < bytes; i++) {
unsigned char c = data[offset + i];
if (::isprint(c))
output += c;
else
output += '.';
}
output += '*';
::Log(level, "%04X: %s", offset, output.c_str());
offset += 16U;
if (length >= 16U)
length -= 16U;
else
length = 0U;
}
}
void CUtils::dump(const std::string& title, const bool* bits, unsigned int length)
{
assert(bits != NULL);
dump(2U, title, bits, length);
}
void CUtils::dump(int level, const std::string& title, const bool* bits, unsigned int length)
{
assert(bits != NULL);
unsigned char bytes[100U];
unsigned int nBytes = 0U;
for (unsigned int n = 0U; n < length; n += 8U, nBytes++)
bitsToByteBE(bits + n, bytes[nBytes]);
dump(level, title, bytes, nBytes);
}
void CUtils::byteToBitsBE(unsigned char byte, bool* bits)
{
assert(bits != NULL);
bits[0U] = (byte & 0x80U) == 0x80U;
bits[1U] = (byte & 0x40U) == 0x40U;
bits[2U] = (byte & 0x20U) == 0x20U;
bits[3U] = (byte & 0x10U) == 0x10U;
bits[4U] = (byte & 0x08U) == 0x08U;
bits[5U] = (byte & 0x04U) == 0x04U;
bits[6U] = (byte & 0x02U) == 0x02U;
bits[7U] = (byte & 0x01U) == 0x01U;
}
void CUtils::byteToBitsLE(unsigned char byte, bool* bits)
{
assert(bits != NULL);
bits[0U] = (byte & 0x01U) == 0x01U;
bits[1U] = (byte & 0x02U) == 0x02U;
bits[2U] = (byte & 0x04U) == 0x04U;
bits[3U] = (byte & 0x08U) == 0x08U;
bits[4U] = (byte & 0x10U) == 0x10U;
bits[5U] = (byte & 0x20U) == 0x20U;
bits[6U] = (byte & 0x40U) == 0x40U;
bits[7U] = (byte & 0x80U) == 0x80U;
}
void CUtils::bitsToByteBE(const bool* bits, unsigned char& byte)
{
assert(bits != NULL);
byte = bits[0U] ? 0x80U : 0x00U;
byte |= bits[1U] ? 0x40U : 0x00U;
byte |= bits[2U] ? 0x20U : 0x00U;
byte |= bits[3U] ? 0x10U : 0x00U;
byte |= bits[4U] ? 0x08U : 0x00U;
byte |= bits[5U] ? 0x04U : 0x00U;
byte |= bits[6U] ? 0x02U : 0x00U;
byte |= bits[7U] ? 0x01U : 0x00U;
}
void CUtils::bitsToByteLE(const bool* bits, unsigned char& byte)
{
assert(bits != NULL);
byte = bits[0U] ? 0x01U : 0x00U;
byte |= bits[1U] ? 0x02U : 0x00U;
byte |= bits[2U] ? 0x04U : 0x00U;
byte |= bits[3U] ? 0x08U : 0x00U;
byte |= bits[4U] ? 0x10U : 0x00U;
byte |= bits[5U] ? 0x20U : 0x00U;
byte |= bits[6U] ? 0x40U : 0x00U;
byte |= bits[7U] ? 0x80U : 0x00U;
}

36
USRP2M17/Utils.h Normal file
View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2009,2014,2015 by Jonathan Naylor, G4KLX
*
* 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; version 2 of the License.
*
* 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.
*/
#ifndef Utils_H
#define Utils_H
#include <string>
class CUtils {
public:
static void dump(const std::string& title, const unsigned char* data, unsigned int length);
static void dump(int level, const std::string& title, const unsigned char* data, unsigned int length);
static void dump(const std::string& title, const bool* bits, unsigned int length);
static void dump(int level, const std::string& title, const bool* bits, unsigned int length);
static void byteToBitsBE(unsigned char byte, bool* bits);
static void byteToBitsLE(unsigned char byte, bool* bits);
static void bitsToByteBE(const bool* bits, unsigned char& byte);
static void bitsToByteLE(const bool* bits, unsigned char& byte);
private:
};
#endif

25
USRP2M17/Version.h Normal file
View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX
* Copyright (C) 2018 by Andy Uribe CA6JAU
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#if !defined(VERSION_H)
#define VERSION_H
const char* VERSION = "20210406";
#endif

View File

@ -0,0 +1,964 @@
/*
* This intermediary file and the files that used to create it are under
* The LGPL. See the file COPYING.
*/
#include "defines.h"
/* codebook/lsp1.txt */
static float codes00[] =
{
225,
250,
275,
300,
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600
};
/* codebook/lsp2.txt */
static float codes01[] =
{
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600,
625,
650,
675,
700
};
/* codebook/lsp3.txt */
static float codes02[] =
{
500,
550,
600,
650,
700,
750,
800,
850,
900,
950,
1000,
1050,
1100,
1150,
1200,
1250
};
/* codebook/lsp4.txt */
static float codes03[] =
{
700,
800,
900,
1000,
1100,
1200,
1300,
1400,
1500,
1600,
1700,
1800,
1900,
2000,
2100,
2200
};
/* codebook/lsp5.txt */
static float codes04[] =
{
950,
1050,
1150,
1250,
1350,
1450,
1550,
1650,
1750,
1850,
1950,
2050,
2150,
2250,
2350,
2450
};
/* codebook/lsp6.txt */
static float codes05[] =
{
1100,
1200,
1300,
1400,
1500,
1600,
1700,
1800,
1900,
2000,
2100,
2200,
2300,
2400,
2500,
2600
};
/* codebook/lsp7.txt */
static float codes06[] =
{
1500,
1600,
1700,
1800,
1900,
2000,
2100,
2200,
2300,
2400,
2500,
2600,
2700,
2800,
2900,
3000
};
/* codebook/lsp8.txt */
static float codes07[] =
{
2300,
2400,
2500,
2600,
2700,
2800,
2900,
3000
};
/* codebook/lsp9.txt */
static float codes08[] =
{
2500,
2600,
2700,
2800,
2900,
3000,
3100,
3200
};
/* codebook/lsp10.txt */
static float codes09[] =
{
2900,
3100,
3300,
3500
};
const struct lsp_codebook lsp_cb[] =
{
/* codebook/lsp1.txt */
{
1,
4,
16,
codes00
},
/* codebook/lsp2.txt */
{
1,
4,
16,
codes01
},
/* codebook/lsp3.txt */
{
1,
4,
16,
codes02
},
/* codebook/lsp4.txt */
{
1,
4,
16,
codes03
},
/* codebook/lsp5.txt */
{
1,
4,
16,
codes04
},
/* codebook/lsp6.txt */
{
1,
4,
16,
codes05
},
/* codebook/lsp7.txt */
{
1,
4,
16,
codes06
},
/* codebook/lsp8.txt */
{
1,
3,
8,
codes07
},
/* codebook/lsp9.txt */
{
1,
3,
8,
codes08
},
/* codebook/lsp10.txt */
{
1,
2,
4,
codes09
},
{ 0, 0, 0, 0 }
};
/* codebook/dlsp1.txt */
static float codes10[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
225,
250,
275,
300,
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600,
625,
650,
675,
700,
725,
750,
775,
800
};
/* codebook/dlsp2.txt */
static float codes11[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
225,
250,
275,
300,
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600,
625,
650,
675,
700,
725,
750,
775,
800
};
/* codebook/dlsp3.txt */
static float codes12[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
225,
250,
275,
300,
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600,
625,
650,
675,
700,
725,
750,
775,
800
};
/* codebook/dlsp4.txt */
static float codes13[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
250,
300,
350,
400,
450,
500,
550,
600,
650,
700,
750,
800,
850,
900,
950,
1000,
1050,
1100,
1150,
1200,
1250,
1300,
1350,
1400
};
/* codebook/dlsp5.txt */
static float codes14[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
250,
300,
350,
400,
450,
500,
550,
600,
650,
700,
750,
800,
850,
900,
950,
1000,
1050,
1100,
1150,
1200,
1250,
1300,
1350,
1400
};
/* codebook/dlsp6.txt */
static float codes15[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
250,
300,
350,
400,
450,
500,
550,
600,
650,
700,
750,
800,
850,
900,
950,
1000,
1050,
1100,
1150,
1200,
1250,
1300,
1350,
1400
};
/* codebook/dlsp7.txt */
static float codes16[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
225,
250,
275,
300,
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600,
625,
650,
675,
700,
725,
750,
775,
800
};
/* codebook/dlsp8.txt */
static float codes17[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
225,
250,
275,
300,
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600,
625,
650,
675,
700,
725,
750,
775,
800
};
/* codebook/dlsp9.txt */
static float codes18[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
225,
250,
275,
300,
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600,
625,
650,
675,
700,
725,
750,
775,
800
};
/* codebook/dlsp10.txt */
static float codes19[] =
{
25,
50,
75,
100,
125,
150,
175,
200,
225,
250,
275,
300,
325,
350,
375,
400,
425,
450,
475,
500,
525,
550,
575,
600,
625,
650,
675,
700,
725,
750,
775,
800
};
const struct lsp_codebook lsp_cbd[] =
{
/* codebook/dlsp1.txt */
{
1,
5,
32,
codes10
},
/* codebook/dlsp2.txt */
{
1,
5,
32,
codes11
},
/* codebook/dlsp3.txt */
{
1,
5,
32,
codes12
},
/* codebook/dlsp4.txt */
{
1,
5,
32,
codes13
},
/* codebook/dlsp5.txt */
{
1,
5,
32,
codes14
},
/* codebook/dlsp6.txt */
{
1,
5,
32,
codes15
},
/* codebook/dlsp7.txt */
{
1,
5,
32,
codes16
},
/* codebook/dlsp8.txt */
{
1,
5,
32,
codes17
},
/* codebook/dlsp9.txt */
{
1,
5,
32,
codes18
},
/* codebook/dlsp10.txt */
{
1,
5,
32,
codes19
},
{ 0, 0, 0, 0 }
};
/* codebook/gecb.txt */
static float codes30[] =
{
2.71, 12.0184,
0.04675, -2.73881,
0.120993, 8.38895,
-1.58028, -0.892307,
1.19307, -1.91561,
0.187101, -3.27679,
0.332251, -7.66455,
-1.47944, 31.2461,
1.52761, 27.7095,
-0.524379, 5.25012,
0.55333, 7.4388,
-0.843451, -1.95299,
2.26389, 8.61029,
0.143143, 2.36549,
0.616506, 1.28427,
-1.71133, 22.0967,
1.00813, 17.3965,
-0.106718, 1.41891,
-0.136246, 14.2736,
-1.70909, -20.5319,
1.65787, -3.39107,
0.138049, -4.95785,
0.536729, -1.94375,
0.196307, 36.8519,
1.27248, 22.5565,
-0.670219, -1.90604,
0.382092, 6.40113,
-0.756911, -4.90102,
1.82931, 4.6138,
0.318794, 0.73683,
0.612815, -2.07505,
-0.410151, 24.7871,
1.77602, 13.1909,
0.106457, -0.104492,
0.192206, 10.1838,
-1.82442, -7.71565,
0.931346, 4.34835,
0.308813, -4.086,
0.397143, -11.8089,
-0.048715, 41.2273,
0.877342, 35.8503,
-0.759794, 0.476634,
0.978593, 7.67467,
-1.19506, 3.03883,
2.63989, -3.41106,
0.191127, 3.60351,
0.402932, 1.0843,
-2.15202, 18.1076,
1.5468, 8.32271,
-0.143089, -4.07592,
-0.150142, 5.86674,
-1.40844, -3.2507,
1.56615, -10.4132,
0.178171, -10.2267,
0.362164, -0.028556,
-0.070125, 24.3907,
0.594752, 17.4828,
-0.28698, -6.90407,
0.464818, 10.2055,
-1.00684, -14.3572,
2.32957, -3.69161,
0.335745, 2.40714,
1.01966, -3.15565,
-1.25945, 7.9919,
2.38369, 19.6806,
-0.094947, -2.41374,
0.20933, 6.66477,
-2.22103, 1.37986,
1.29239, 2.04633,
0.243626, -0.890741,
0.428773, -7.19366,
-1.11374, 41.3414,
2.6098, 31.1405,
-0.446468, 2.53419,
0.490104, 4.62757,
-1.11723, -3.24174,
1.79156, 8.41493,
0.156012, 0.183336,
0.532447, 3.15455,
-0.764484, 18.514,
0.952395, 11.7713,
-0.332567, 0.346987,
0.202165, 14.7168,
-2.12924, -15.559,
1.35358, -1.92679,
-0.010963, -16.3364,
0.399053, -2.79057,
0.750657, 31.1483,
0.655743, 24.4819,
-0.45321, -0.735879,
0.2869, 6.5467,
-0.715673, -12.3578,
1.54849, 3.87217,
0.271874, 0.802339,
0.502073, -4.85485,
-0.497037, 17.7619,
1.19116, 13.9544,
0.01563, 1.33157,
0.341867, 8.93537,
-2.31601, -5.39506,
0.75861, 1.9645,
0.24132, -3.23769,
0.267151, -11.2344,
-0.273126, 32.6248,
1.75352, 40.432,
-0.784011, 3.04576,
0.705987, 5.66118,
-1.3864, 1.35356,
2.37646, 1.67485,
0.242973, 4.73218,
0.491227, 0.354061,
-1.60676, 8.65895,
1.16711, 5.9871,
-0.137601, -12.0417,
-0.251375, 10.3972,
-1.43151, -8.90411,
0.98828, -13.209,
0.261484, -6.35497,
0.395932, -0.702529,
0.283704, 26.8996,
0.420959, 15.4418,
-0.355804, -13.7278,
0.527372, 12.3985,
-1.16956, -15.9985,
1.90669, -5.81605,
0.354492, 3.85157,
0.82576, -4.16264,
-0.49019, 13.0572,
2.25577, 13.5264,
-0.004956, -3.23713,
0.026709, 7.86645,
-1.81037, -0.451183,
1.08383, -0.18362,
0.135836, -2.26658,
0.375812, -5.51225,
-1.96644, 38.6829,
1.97799, 24.5655,
-0.704656, 6.35881,
0.480786, 7.05175,
-0.976417, -2.42273,
2.50215, 6.75935,
0.083588, 3.2588,
0.543629, 0.910013,
-1.23196, 23.0915,
0.785492, 14.807,
-0.213554, 1.688,
0.004748, 18.1718,
-1.54719, -16.1168,
1.50104, -3.28114,
0.080133, -4.63472,
0.476592, -2.18093,
0.44247, 40.304,
1.07277, 27.592,
-0.594738, -4.16681,
0.42248, 7.61609,
-0.927521, -7.27441,
1.99162, 1.29636,
0.291307, 2.39878,
0.721081, -1.95062,
-0.804256, 24.9295,
1.64839, 19.1197,
0.060852, -0.590639,
0.266085, 9.10325,
-1.9574, -2.88461,
1.11693, 2.6724,
0.35458, -2.74854,
0.330733, -14.1561,
-0.527851, 39.5756,
0.991152, 43.195,
-0.589619, 1.26919,
0.787401, 8.73071,
-1.0138, 1.02507,
2.8254, 1.89538,
0.24089, 2.74557,
0.427195, 2.54446,
-1.95311, 12.244,
1.44862, 12.0607,
-0.210492, -3.37906,
-0.056713, 10.204,
-1.65237, -5.10274,
1.29475, -12.2708,
0.111608, -8.67592,
0.326634, -1.16763,
0.021781, 31.1258,
0.455335, 21.4684,
-0.37544, -3.37121,
0.39362, 11.302,
-0.851456, -19.4149,
2.10703, -2.22886,
0.373233, 1.92406,
0.884438, -1.72058,
-0.975127, 9.84013,
2.0033, 17.3954,
-0.036915, -1.11137,
0.148456, 5.39997,
-1.91441, 4.77382,
1.44791, 0.537122,
0.194979, -1.03818,
0.495771, -9.95502,
-1.05899, 32.9471,
2.01122, 32.4544,
-0.30965, 4.71911,
0.436082, 4.63552,
-1.23711, -1.25428,
2.02274, 9.42834,
0.190342, 1.46077,
0.479017, 2.48479,
-1.07848, 16.2217,
1.20764, 9.65421,
-0.258087, -1.67236,
0.071852, 13.416,
-1.87723, -16.072,
1.28957, -4.87118,
0.067713, -13.4427,
0.435551, -4.1655,
0.46614, 30.5895,
0.904895, 21.598,
-0.518369, -2.53205,
0.337363, 5.63726,
-0.554975, -17.4005,
1.69188, 1.14574,
0.227934, 0.889297,
0.587303, -5.72973,
-0.262133, 18.6666,
1.39505, 17.0029,
-0.01909, 4.30838,
0.304235, 12.6699,
-2.07406, -6.46084,
0.920546, 1.21296,
0.284927, -1.78547,
0.209724, -16.024,
-0.636067, 31.5768,
1.34989, 34.6775,
-0.971625, 5.30086,
0.590249, 4.44971,
-1.56787, 3.60239,
2.1455, 4.51666,
0.296022, 4.12017,
0.445299, 0.868772,
-1.44193, 14.1284,
1.35575, 6.0074,
-0.012814, -7.49657,
-0.43, 8.50012,
-1.20469, -7.11326,
1.10102, -6.83682,
0.196463, -6.234,
0.436747, -1.12979,
0.141052, 22.8549,
0.290821, 18.8114,
-0.529536, -7.73251,
0.63428, 10.7898,
-1.33472, -20.3258,
1.81564, -1.90332,
0.394778, 3.79758,
0.732682, -8.18382,
-0.741244, 11.7683
};
const struct lsp_codebook ge_cb[] =
{
/* codebook/gecb.txt */
{
2,
8,
256,
codes30
},
{ 0, 0, 0, 0 }
};

1745
USRP2M17/codec2/codec2.cpp Normal file

File diff suppressed because it is too large Load Diff

100
USRP2M17/codec2/codec2.h Normal file
View File

@ -0,0 +1,100 @@
/*---------------------------------------------------------------------------*\
FILE........: codec2.h
AUTHOR......: David Rowe
DATE CREATED: 21 August 2010
Codec 2 fully quantised encoder and decoder functions. If you want use
Codec 2, these are the functions you need to call.
\*---------------------------------------------------------------------------*/
/*
Copyright (C) 2010 David Rowe
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __CODEC2__
#define __CODEC2__
#include <complex>
#include "codec2_internal.h"
#include "defines.h"
#include "kiss_fft.h"
#include "nlp.h"
#include "quantise.h"
#define CODEC2_MODE_3200 0
#define CODEC2_MODE_1600 2
#ifndef CODEC2_MODE_EN_DEFAULT
#define CODEC2_MODE_EN_DEFAULT 1
#endif
#define CODEC2_RAND_MAX 32767
class CCodec2
{
public:
CCodec2(bool is_3200);
~CCodec2();
void codec2_encode(unsigned char *bits, const short *speech_in);
void codec2_decode(short *speech_out, const unsigned char *bits);
void codec2_set_mode(bool);
bool codec2_get_mode() {return (c2.mode == 3200); };
int codec2_samples_per_frame();
int codec2_bits_per_frame();
private:
// merged from other files
void sample_phase(MODEL *model, std::complex<float> filter_phase[], std::complex<float> A[]);
void phase_synth_zero_order(int n_samp, MODEL *model, float *ex_phase, std::complex<float> filter_phase[]);
void postfilter(MODEL *model, float *bg_est);
C2CONST c2const_create(int Fs, float framelength_ms);
void make_analysis_window(C2CONST *c2const, FFT_STATE *fft_fwd_cfg, float w[], float W[]);
void dft_speech(C2CONST *c2const, FFT_STATE &fft_fwd_cfg, std::complex<float> Sw[], float Sn[], float w[]);
void two_stage_pitch_refinement(C2CONST *c2const, MODEL *model, std::complex<float> Sw[]);
void estimate_amplitudes(MODEL *model, std::complex<float> Sw[], int est_phase);
float est_voicing_mbe(C2CONST *c2const, MODEL *model, std::complex<float> Sw[], float W[]);
void make_synthesis_window(C2CONST *c2const, float Pn[]);
void synthesise(int n_samp, FFTR_STATE *fftr_inv_cfg, float Sn_[], MODEL *model, float Pn[], int shift);
int codec2_rand(void);
void hs_pitch_refinement(MODEL *model, std::complex<float> Sw[], float pmin, float pmax, float pstep);
void interp_Wo(MODEL *interp, MODEL *prev, MODEL *next, float Wo_min);
void interp_Wo2(MODEL *interp, MODEL *prev, MODEL *next, float weight, float Wo_min);
float interp_energy(float prev, float next);
void interpolate_lsp_ver2(float interp[], float prev[], float next[], float weight, int order);
void analyse_one_frame(MODEL *model, const short *speech);
void synthesise_one_frame(short speech[], MODEL *model, std::complex<float> Aw[], float gain);
void codec2_encode_3200(unsigned char *bits, const short *speech);
void codec2_encode_1600(unsigned char *bits, const short *speech);
void codec2_decode_3200(short *speech, const unsigned char *bits);
void codec2_decode_1600(short *speech, const unsigned char *bits);
void ear_protection(float in_out[], int n);
void lsp_to_lpc(float *freq, float *ak, int lpcrdr);
void (CCodec2::*encode)(unsigned char *bits, const short *speech);
void (CCodec2::*decode)(short *speech, const unsigned char *bits);
Cnlp nlp;
CQuantize qt;
CODEC2 c2;
};
#endif

View File

@ -0,0 +1,67 @@
/*---------------------------------------------------------------------------*\
FILE........: codec2_internal.h
AUTHOR......: David Rowe
DATE CREATED: April 16 2012
Header file for Codec2 internal states, exposed via this header
file to assist in testing.
\*---------------------------------------------------------------------------*/
/*
Copyright (C) 2012 David Rowe
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __CODEC2_INTERNAL__
#define __CODEC2_INTERNAL__
#include "kiss_fft.h"
using CODEC2 = struct codec2_tag {
int mode;
int Fs;
int n_samp;
int m_pitch;
int gray; /* non-zero for gray encoding */
int lpc_pf; /* LPC post filter on */
int bass_boost; /* LPC post filter bass boost */
int smoothing; /* enable smoothing for channels with errors */
float ex_phase; /* excitation model phase track */
float bg_est; /* background noise estimate for post filter */
float prev_f0_enc; /* previous frame's f0 estimate */
float prev_e_dec; /* previous frame's LPC energy */
float beta; /* LPC post filter parameters */
float gamma;
float xq_enc[2]; /* joint pitch and energy VQ states */
float xq_dec[2];
float W[FFT_ENC]; /* DFT of w[] */
float hpf_states[2]; /* high pass filter states */
float prev_lsps_dec[LPC_ORD]; /* previous frame's LSPs */
float *softdec; /* optional soft decn bits from demod */
MODEL prev_model_dec; /* previous frame's model parameters */
C2CONST c2const;
FFT_STATE fft_fwd_cfg; /* forward FFT config */
FFTR_STATE fftr_fwd_cfg; /* forward real FFT config */
FFTR_STATE fftr_inv_cfg; /* inverse FFT config */
std::vector<float> w; /* [m_pitch] time domain hamming window */
std::vector<float> Pn; /* [2*n_samp] trapezoidal synthesis window */
std::vector<float> Sn; /* [m_pitch] input speech */
std::vector<float> Sn_; /* [2*n_samp] synthesised output speech */
std::vector<float> bpf_buf; /* buffer for band pass filter */
};
#endif

127
USRP2M17/codec2/defines.h Normal file
View File

@ -0,0 +1,127 @@
/*---------------------------------------------------------------------------*\
FILE........: defines.h
AUTHOR......: David Rowe
DATE CREATED: 23/4/93
Defines and structures used throughout the codec.
\*---------------------------------------------------------------------------*/
/*
Copyright (C) 2009 David Rowe
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __DEFINES__
#define __DEFINES__
#include <complex>
#include <vector>
/*---------------------------------------------------------------------------*\
DEFINES
\*---------------------------------------------------------------------------*/
/* General defines */
#define N_S 0.01 /* internal proc frame length in secs */
#define TW_S 0.005 /* trapezoidal synth window overlap */
#define MAX_AMP 160 /* maximum number of harmonics */
#ifndef PI
#define PI 3.141592654 /* mathematical constant */
#endif
#define TWO_PI 6.283185307 /* mathematical constant */
#define MAX_STR 2048 /* maximum string size */
#define FFT_ENC 512 /* size of FFT used for encoder */
#define FFT_DEC 512 /* size of FFT used in decoder */
#define V_THRESH 6.0 /* voicing threshold in dB */
#define LPC_ORD 10 /* LPC order */
#define LPC_ORD_LOW 6 /* LPC order for lower rates */
/* Pitch estimation defines */
#define M_PITCH_S 0.0400 /* pitch analysis window in s */
#define P_MIN_S 0.0025 /* minimum pitch period in s */
#define P_MAX_S 0.0200 /* maximum pitch period in s */
#define MAXFACTORS 32 // e.g. an fft of length 128 has 4 factors
// as far as kissfft is concerned 4*4*4*2
/*---------------------------------------------------------------------------*\
TYPEDEFS
\*---------------------------------------------------------------------------*/
/* Structure to hold constants calculated at run time based on sample rate */
using C2CONST = struct c2const_tag
{
int Fs; /* sample rate of this instance */
int n_samp; /* number of samples per 10ms frame at Fs */
int max_amp; /* maximum number of harmonics */
int m_pitch; /* pitch estimation window size in samples */
int p_min; /* minimum pitch period in samples */
int p_max; /* maximum pitch period in samples */
float Wo_min;
float Wo_max;
int nw; /* analysis window size in samples */
int tw; /* trapezoidal synthesis window overlap */
};
/* Structure to hold model parameters for one frame */
using MODEL = struct model_tag
{
float Wo; /* fundamental frequency estimate in radians */
int L; /* number of harmonics */
float A[MAX_AMP+1]; /* amplitiude of each harmonic */
float phi[MAX_AMP+1]; /* phase of each harmonic */
int voiced; /* non-zero if this frame is voiced */
};
/* describes each codebook */
struct lsp_codebook
{
int k; /* dimension of vector */
int log2m; /* number of bits in m */
int m; /* elements in codebook */
float *cb; /* The elements */
};
using FFT_STATE = struct fft_state_tag
{
int nfft;
bool inverse;
int factors[2*MAXFACTORS];
std::vector<std::complex<float>> twiddles;
};
using FFTR_STATE = struct fftr_state_tag
{
FFT_STATE substate;
std::vector<std::complex<float>> tmpbuf;
std::vector<std::complex<float>> super_twiddles;
};
extern const struct lsp_codebook lsp_cb[];
extern const struct lsp_codebook lsp_cbd[];
extern const struct lsp_codebook ge_cb[];
#endif

View File

@ -0,0 +1,435 @@
/*
Copyright (c) 2003-2010, Mark Borgerding
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <cstring>
#include <cassert>
#include "defines.h"
#include "kiss_fft.h"
void CKissFFT::kf_bfly2(std::complex<float> *Fout, const size_t fstride, FFT_STATE &st, int m)
{
std::complex<float> *Fout2;
std::complex<float> *tw1 = st.twiddles.data();
std::complex<float> t;
Fout2 = Fout + m;
do
{
t = *Fout2 * *tw1;
tw1 += fstride;
*Fout2 = *Fout - t;
*Fout += t;
++Fout2;
++Fout;
}
while (--m);
}
void CKissFFT::kf_bfly3(std::complex<float> * Fout, const size_t fstride, FFT_STATE &st, int m)
{
const size_t m2 = 2 * m;
std::complex<float> *tw1,*tw2;
std::complex<float> scratch[5];
std::complex<float> epi3;
epi3 = st.twiddles[fstride*m];
tw1 = tw2 = st.twiddles.data();
do
{
scratch[1] = Fout[m] * *tw1;
scratch[2] = Fout[m2] * *tw2;
scratch[3] = scratch[1] + scratch[2];
scratch[0] = scratch[1] - scratch[2];
tw1 += fstride;
tw2 += fstride*2;
Fout[m] = *Fout - (0.5f * scratch[3]);
scratch[0] *= epi3.imag();
*Fout += scratch[3];
Fout[m2].real(Fout[m].real() + scratch[0].imag());
Fout[m2].imag(Fout[m].imag() - scratch[0].real());
Fout[m].real(Fout[m].real() - scratch[0].imag());
Fout[m].imag(Fout[m].imag() + scratch[0].real());
++Fout;
}
while(--m);
}
void CKissFFT::kf_bfly4(std::complex<float> *Fout, const size_t fstride, FFT_STATE &st, int m)
{
std::complex<float> *tw1,*tw2,*tw3;
std::complex<float> scratch[6];
int k = m;
const int m2 = 2 * m;
const int m3 = 3 * m;
tw3 = tw2 = tw1 = st.twiddles.data();
do
{
scratch[0] = Fout[m] * *tw1;
scratch[1] = Fout[m2] * *tw2;
scratch[2] = Fout[m3] * *tw3;
scratch[5] = *Fout - scratch[1];
*Fout += scratch[1];
scratch[3] = scratch[0] + scratch[2];
scratch[4] = scratch[0] - scratch[2];
Fout[m2] = *Fout - scratch[3];
tw1 += fstride;
tw2 += fstride*2;
tw3 += fstride*3;
*Fout += scratch[3];
if(st.inverse)
{
Fout[m].real(scratch[5].real() - scratch[4].imag());
Fout[m].imag(scratch[5].imag() + scratch[4].real());
Fout[m3].real(scratch[5].real() + scratch[4].imag());
Fout[m3].imag(scratch[5].imag() - scratch[4].real());
}
else
{
Fout[m].real(scratch[5].real() + scratch[4].imag());
Fout[m].imag(scratch[5].imag() - scratch[4].real());
Fout[m3].real(scratch[5].real() - scratch[4].imag());
Fout[m3].imag(scratch[5].imag() + scratch[4].real());
}
++Fout;
}
while(--k);
}
void CKissFFT::kf_bfly5(std::complex<float> * Fout, const size_t fstride, FFT_STATE &st, int m)
{
std::complex<float> scratch[13];
std::complex<float> *twiddles = st.twiddles.data();
auto ya = twiddles[fstride*m];
auto yb = twiddles[fstride*2*m];
auto Fout0 = Fout;
auto Fout1 = Fout0 + m;
auto Fout2 = Fout0 + 2 * m;
auto Fout3 = Fout0 + 3 * m;
auto Fout4 = Fout0 + 4 * m;
auto tw = st.twiddles.data();
for (int u=0; u<m; ++u)
{
scratch[0] = *Fout0;
scratch[1] = *Fout1 * tw[u*fstride];
scratch[2] = *Fout2 * tw[2*u*fstride];
scratch[3] = *Fout3 * tw[3*u*fstride];
scratch[4] = *Fout4 * tw[4*u*fstride];
scratch[7] = scratch[1] + scratch[4];
scratch[10] = scratch[1] - scratch[4];
scratch[8] = scratch[2] + scratch[3];
scratch[9] = scratch[2] - scratch[3];
*Fout0 += scratch[7] + scratch[8];
scratch[5] = scratch[0] + (scratch[7] * ya.real()) + (scratch[8] * yb.real());
scratch[6].real( (scratch[10].imag() * ya.imag()) + (scratch[9].imag() * yb.imag()));
scratch[6].imag(-(scratch[10].real() * ya.imag()) - (scratch[9].real() * yb.imag()));
*Fout1 = scratch[5] - scratch[6];
*Fout4 = scratch[5] + scratch[6];
scratch[11] = scratch[0] + (scratch[7] * yb.real()) + (scratch[8] * ya.real());
scratch[12].real(-(scratch[10].imag() * yb.imag()) + (scratch[9].imag() * ya.imag()));
scratch[12].imag( (scratch[10].real() * yb.imag()) - (scratch[9].real() * ya.imag()));
*Fout2 = scratch[11] + scratch[12];
*Fout3 = scratch[11] - scratch[12];
++Fout0;
++Fout1;
++Fout2;
++Fout3;
++Fout4;
}
}
/* perform the butterfly for one stage of a mixed radix FFT */
void CKissFFT::kf_bfly_generic(std::complex<float> *Fout, const size_t fstride, FFT_STATE &st, int m, int p)
{
auto twiddles = st.twiddles.data();
std::complex<float> t;
int Norig = st.nfft;
std::vector<std::complex<float>> scratch(p);
for (int u=0; u<m; ++u)
{
int k = u;
for (int q1=0 ; q1<p ; ++q1)
{
scratch[q1] = Fout[k];
k += m;
}
k = u;
for (int q1=0 ; q1<p ; ++q1)
{
int twidx = 0;
Fout[k] = scratch[0];
for (int q=1; q<p; ++q)
{
twidx += fstride * k;
if (twidx >= Norig) twidx-=Norig;
t = scratch[q] * twiddles[twidx];
Fout[k] += t;
}
k += m;
}
}
scratch.clear();
}
void CKissFFT::kf_work(std::complex<float> *Fout, const std::complex<float> *f, const size_t fstride, int in_stride, int *factors, FFT_STATE &st)
{
auto Fout_beg = Fout;
const int p = *factors++; /* the radix */
const int m = *factors++; /* stage's fft length/p */
const std::complex<float> *Fout_end = Fout + p*m;
if (m==1)
{
do
{
*Fout = *f;
f += fstride*in_stride;
}
while( ++Fout != Fout_end );
}
else
{
do
{
// recursive call:
// DFT of size m*p performed by doing
// p instances of smaller DFTs of size m,
// each one takes a decimated version of the input
kf_work( Fout, f, fstride*p, in_stride, factors, st);
f += fstride*in_stride;
}
while( (Fout += m) != Fout_end );
}
Fout=Fout_beg;
// recombine the p smaller DFTs
switch (p)
{
case 2:
kf_bfly2(Fout,fstride,st,m);
break;
case 3:
kf_bfly3(Fout,fstride,st,m);
break;
case 4:
kf_bfly4(Fout,fstride,st,m);
break;
case 5:
kf_bfly5(Fout,fstride,st,m);
break;
default:
kf_bfly_generic(Fout,fstride,st,m,p);
break;
}
}
/* facbuf is populated by p1,m1,p2,m2, ...
where
p[i] * m[i] = m[i-1]
m0 = n */
void CKissFFT::kf_factor(int n,int * facbuf)
{
int p=4;
double floor_sqrt;
floor_sqrt = floorf( sqrtf((double)n) );
/*factor out powers of 4, powers of 2, then any remaining primes */
do
{
while (n % p)
{
switch (p)
{
case 4:
p = 2;
break;
case 2:
p = 3;
break;
default:
p += 2;
break;
}
if (p > floor_sqrt)
p = n; /* no more factors, skip to end */
}
n /= p;
*facbuf++ = p;
*facbuf++ = n;
}
while (n > 1);
}
void CKissFFT::fft_alloc(FFT_STATE &state, const int nfft, bool inverse_fft)
{
state.twiddles.resize(nfft);
state.nfft = nfft;
state.inverse = inverse_fft;
for (int i=0; i<nfft; ++i)
{
const double pi=3.141592653589793238462643383279502884197169399375105820974944;
double phase = -2.0 * pi * i / nfft;
if (state.inverse)
phase *= -1.0;
state.twiddles[i] = std::polar(1.0f, float(phase));
}
kf_factor(nfft, state.factors);
}
void CKissFFT::fft_stride(FFT_STATE &st, const std::complex<float> *fin, std::complex<float> *fout, int in_stride)
{
if (fin == fout)
{
//NOTE: this is not really an in-place FFT algorithm.
//It just performs an out-of-place FFT into a temp buffer
std::vector<std::complex<float>> tmpbuf(st.nfft);
kf_work(tmpbuf.data(), fin, true, in_stride, st.factors, st);
memcpy(fout, tmpbuf.data(), sizeof(std::complex<float>)*st.nfft);
tmpbuf.clear();
}
else
{
kf_work(fout, fin, 1, in_stride, st.factors, st);
}
}
void CKissFFT::fft(FFT_STATE &cfg, const std::complex<float> *fin, std::complex<float> *fout)
{
fft_stride(cfg, fin, fout, 1);
}
int CKissFFT::fft_next_fast_size(int n)
{
while(1)
{
int m = n;
while ( (m % 2) == 0 ) m /= 2;
while ( (m % 3) == 0 ) m /= 3;
while ( (m % 5) == 0 ) m /= 5;
if (m <= 1)
break; /* n is completely factorable by twos, threes, and fives */
n++;
}
return n;
}
void CKissFFT::fftr_alloc(FFTR_STATE &st, int nfft, const bool inverse_fft)
{
nfft >>= 1;
fft_alloc(st.substate, nfft, inverse_fft);
st.tmpbuf.resize(nfft);
st.super_twiddles.resize(nfft);
for (int i=0; i<nfft/2; ++i)
{
double phase = -3.141592653589793238462643383279502884197169399375105820974944 * (double(i+1) / nfft + .5);
if (inverse_fft)
phase *= -1.0;
st.super_twiddles[i] = std::polar(1.0f, float(phase));
}
}
void CKissFFT::fftr(FFTR_STATE &st, const float *timedata, std::complex<float> *freqdata)
{
assert(st.substate.inverse == false);
auto ncfft = st.substate.nfft;
/*perform the parallel fft of two real signals packed in real,imag*/
fft( st.substate, (const std::complex<float>*)timedata, st.tmpbuf.data());
/* The real part of the DC element of the frequency spectrum in st->tmpbuf
* contains the sum of the even-numbered elements of the input time sequence
* The imag part is the sum of the odd-numbered elements
*
* The sum of tdc.r and tdc.i is the sum of the input time sequence.
* yielding DC of input time sequence
* The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1...
* yielding Nyquist bin of input time sequence
*/
auto tdc = st.tmpbuf[0];
freqdata[0].real(tdc.real() + tdc.imag());
freqdata[ncfft].real(tdc.real() - tdc.imag());
freqdata[ncfft].imag(0.f);
freqdata[0].imag(0.f);
for (int k=1; k <= ncfft/2; ++k)
{
auto fpk = st.tmpbuf[k];
auto fpnk = std::conj(st.tmpbuf[ncfft-k]);
auto f1k = fpk + fpnk;
auto f2k = fpk - fpnk;
auto tw = f2k * st.super_twiddles[k-1];
freqdata[k] = 0.5f * (f1k + tw);
freqdata[ncfft-k].real(0.5f * (f1k.real() - tw.real()));
freqdata[ncfft-k].imag(0.5f * (tw.imag() - f1k.imag()));
}
}
void CKissFFT::fftri(FFTR_STATE &st, const std::complex<float> *freqdata, float *timedata)
{
assert(st.substate.inverse == true);
auto ncfft = st.substate.nfft;
st.tmpbuf[0].real(freqdata[0].real() + freqdata[ncfft].real());
st.tmpbuf[0].imag(freqdata[0].real() - freqdata[ncfft].real());
for (int k=1; k <= ncfft/2; ++k)
{
auto fk = freqdata[k];
auto fnkc = std::conj(freqdata[ncfft - k]);
auto fek = fk + fnkc;
auto tmp = fk - fnkc;
auto fok = tmp * st.super_twiddles[k-1];
st.tmpbuf[k] = fek + fok;
st.tmpbuf[ncfft - k] = std::conj(fek - fok);
}
fft (st.substate, st.tmpbuf.data(), (std::complex<float> *)timedata);
}

View File

@ -0,0 +1,35 @@
#ifndef KISS_FFT_H
#define KISS_FFT_H
#include <complex>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include "defines.h"
/* for real ffts, we need an even size */
#define kiss_fftr_next_fast_size_real(n) (kiss_fft_next_fast_size( ((n)+1) >> 1) << 1 )
class CKissFFT
{
public:
void fft_alloc(FFT_STATE &state, const int nfft, const bool inverse_fft);
void fft(FFT_STATE &cfg, const std::complex<float> *fin, std::complex<float> *fout);
void fft_stride(FFT_STATE &cfg, const std::complex<float> *fin, std::complex<float> *fout, int fin_stride);
int fft_next_fast_size(int n);
void fftr_alloc(FFTR_STATE &state, int nfft, const bool inverse_fft);
void fftr(FFTR_STATE &cfg,const float *timedata,std::complex<float> *freqdata);
void fftri(FFTR_STATE &cfg,const std::complex<float> *freqdata,float *timedata);
private:
void kf_bfly2(std::complex<float> *Fout, const size_t fstride, FFT_STATE &st, int m);
void kf_bfly3(std::complex<float> *Fout, const size_t fstride, FFT_STATE &st, int m);
void kf_bfly4(std::complex<float> *Fout, const size_t fstride, FFT_STATE &st, int m);
void kf_bfly5(std::complex<float> *Fout, const size_t fstride, FFT_STATE &st, int m);
void kf_bfly_generic(std::complex<float> *Fout, const size_t fstride, FFT_STATE &st, int m, int p);
void kf_work(std::complex<float> *Fout, const std::complex<float> *f, const size_t fstride, int in_stride, int *factors, FFT_STATE &st);
void kf_factor(int n, int *facbuf);
};
#endif

311
USRP2M17/codec2/lpc.cpp Normal file
View File

@ -0,0 +1,311 @@
/*---------------------------------------------------------------------------*\
FILE........: lpc.c
AUTHOR......: David Rowe
DATE CREATED: 30 Sep 1990 (!)
Linear Prediction functions written in C.
\*---------------------------------------------------------------------------*/
/*
Copyright (C) 2009-2012 David Rowe
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#define LPC_MAX_N 512 /* maximum no. of samples in frame */
#define PI 3.141592654 /* mathematical constant */
#define ALPHA 1.0
#define BETA 0.94
#include <assert.h>
#include <math.h>
#include "defines.h"
#include "lpc.h"
/*---------------------------------------------------------------------------*\
pre_emp()
Pre-emphasise (high pass filter with zero close to 0 Hz) a frame of
speech samples. Helps reduce dynamic range of LPC spectrum, giving
greater weight and hense a better match to low energy formants.
Should be balanced by de-emphasis of the output speech.
\*---------------------------------------------------------------------------*/
void Clpc::pre_emp(
float Sn_pre[], /* output frame of speech samples */
float Sn[], /* input frame of speech samples */
float *mem, /* Sn[-1]single sample memory */
int Nsam /* number of speech samples to use */
)
{
int i;
for(i=0; i<Nsam; i++)
{
Sn_pre[i] = Sn[i] - ALPHA * mem[0];
mem[0] = Sn[i];
}
}
/*---------------------------------------------------------------------------*\
de_emp()
De-emphasis filter (low pass filter with a pole close to 0 Hz).
\*---------------------------------------------------------------------------*/
void Clpc::de_emp(
float Sn_de[], /* output frame of speech samples */
float Sn[], /* input frame of speech samples */
float *mem, /* Sn[-1]single sample memory */
int Nsam /* number of speech samples to use */
)
{
int i;
for(i=0; i<Nsam; i++)
{
Sn_de[i] = Sn[i] + BETA * mem[0];
mem[0] = Sn_de[i];
}
}
/*---------------------------------------------------------------------------*\
hanning_window()
Hanning windows a frame of speech samples.
\*---------------------------------------------------------------------------*/
void Clpc::hanning_window(
float Sn[], /* input frame of speech samples */
float Wn[], /* output frame of windowed samples */
int Nsam /* number of samples */
)
{
int i; /* loop variable */
for(i=0; i<Nsam; i++)
Wn[i] = Sn[i]*(0.5 - 0.5*cosf(2*PI*(float)i/(Nsam-1)));
}
/*---------------------------------------------------------------------------*\
autocorrelate()
Finds the first P autocorrelation values of an array of windowed speech
samples Sn[].
\*---------------------------------------------------------------------------*/
void Clpc::autocorrelate(
float Sn[], /* frame of Nsam windowed speech samples */
float Rn[], /* array of P+1 autocorrelation coefficients */
int Nsam, /* number of windowed samples to use */
int order /* order of LPC analysis */
)
{
int i,j; /* loop variables */
for(j=0; j<order+1; j++)
{
Rn[j] = 0.0;
for(i=0; i<Nsam-j; i++)
Rn[j] += Sn[i]*Sn[i+j];
}
}
/*---------------------------------------------------------------------------*\
levinson_durbin()
Given P+1 autocorrelation coefficients, finds P Linear Prediction Coeff.
(LPCs) where P is the order of the LPC all-pole model. The Levinson-Durbin
algorithm is used, and is described in:
J. Makhoul
"Linear prediction, a tutorial review"
Proceedings of the IEEE
Vol-63, No. 4, April 1975
\*---------------------------------------------------------------------------*/
void Clpc::levinson_durbin(
float R[], /* order+1 autocorrelation coeff */
float lpcs[], /* order+1 LPC's */
int order /* order of the LPC analysis */
)
{
float a[order+1][order+1];
float sum, e, k;
int i,j; /* loop variables */
e = R[0]; /* Equation 38a, Makhoul */
for(i=1; i<=order; i++)
{
sum = 0.0;
for(j=1; j<=i-1; j++)
sum += a[i-1][j]*R[i-j];
k = -1.0*(R[i] + sum)/e; /* Equation 38b, Makhoul */
if (fabsf(k) > 1.0)
k = 0.0;
a[i][i] = k;
for(j=1; j<=i-1; j++)
a[i][j] = a[i-1][j] + k*a[i-1][i-j]; /* Equation 38c, Makhoul */
e *= (1-k*k); /* Equation 38d, Makhoul */
}
for(i=1; i<=order; i++)
lpcs[i] = a[order][i];
lpcs[0] = 1.0;
}
/*---------------------------------------------------------------------------*\
inverse_filter()
Inverse Filter, A(z). Produces an array of residual samples from an array
of input samples and linear prediction coefficients.
The filter memory is stored in the first order samples of the input array.
\*---------------------------------------------------------------------------*/
void Clpc::inverse_filter(
float Sn[], /* Nsam input samples */
float a[], /* LPCs for this frame of samples */
int Nsam, /* number of samples */
float res[], /* Nsam residual samples */
int order /* order of LPC */
)
{
int i,j; /* loop variables */
for(i=0; i<Nsam; i++)
{
res[i] = 0.0;
for(j=0; j<=order; j++)
res[i] += Sn[i-j]*a[j];
}
}
/*---------------------------------------------------------------------------*\
synthesis_filter()
C version of the Speech Synthesis Filter, 1/A(z). Given an array of
residual or excitation samples, and the the LP filter coefficients, this
function will produce an array of speech samples. This filter structure is
IIR.
The synthesis filter has memory as well, this is treated in the same way
as the memory for the inverse filter (see inverse_filter() notes above).
The difference is that the memory for the synthesis filter is stored in
the output array, wheras the memory of the inverse filter is stored in the
input array.
Note: the calling function must update the filter memory.
\*---------------------------------------------------------------------------*/
void Clpc::synthesis_filter(
float res[], /* Nsam input residual (excitation) samples */
float a[], /* LPCs for this frame of speech samples */
int Nsam, /* number of speech samples */
int order, /* LPC order */
float Sn_[] /* Nsam output synthesised speech samples */
)
{
int i,j; /* loop variables */
/* Filter Nsam samples */
for(i=0; i<Nsam; i++)
{
Sn_[i] = res[i]*a[0];
for(j=1; j<=order; j++)
Sn_[i] -= Sn_[i-j]*a[j];
}
}
/*---------------------------------------------------------------------------*\
find_aks()
This function takes a frame of samples, and determines the linear
prediction coefficients for that frame of samples.
\*---------------------------------------------------------------------------*/
void Clpc::find_aks(
float Sn[], /* Nsam samples with order sample memory */
float a[], /* order+1 LPCs with first coeff 1.0 */
int Nsam, /* number of input speech samples */
int order, /* order of the LPC analysis */
float *E /* residual energy */
)
{
float Wn[LPC_MAX_N]; /* windowed frame of Nsam speech samples */
float R[order+1]; /* order+1 autocorrelation values of Sn[] */
int i;
assert(Nsam < LPC_MAX_N);
hanning_window(Sn,Wn,Nsam);
autocorrelate(Wn,R,Nsam,order);
levinson_durbin(R,a,order);
*E = 0.0;
for(i=0; i<=order; i++)
*E += a[i]*R[i];
if (*E < 0.0)
*E = 1E-12;
}
/*---------------------------------------------------------------------------*\
weight()
Weights a vector of LPCs.
\*---------------------------------------------------------------------------*/
void Clpc::weight(
float ak[], /* vector of order+1 LPCs */
float gamma, /* weighting factor */
int order, /* num LPCs (excluding leading 1.0) */
float akw[] /* weighted vector of order+1 LPCs */
)
{
int i;
for(i=1; i<=order; i++)
akw[i] = ak[i]*powf(gamma,(float)i);
}

47
USRP2M17/codec2/lpc.h Normal file
View File

@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------*\
FILE........: lpc.h
AUTHOR......: David Rowe
DATE CREATED: 24/8/09
Linear Prediction functions written in C.
\*---------------------------------------------------------------------------*/
/*
Copyright (C) 2009-2012 David Rowe
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __LPC__
#define __LPC__
#define LPC_MAX_ORDER 20
class Clpc {
public:
void autocorrelate(float Sn[], float Rn[], int Nsam, int order);
void levinson_durbin(float R[], float lpcs[], int order);
private:
void pre_emp(float Sn_pre[], float Sn[], float *mem, int Nsam);
void de_emp(float Sn_se[], float Sn[], float *mem, int Nsam);
void hanning_window(float Sn[], float Wn[], int Nsam);
void inverse_filter(float Sn[], float a[], int Nsam, float res[], int order);
void synthesis_filter(float res[], float a[], int Nsam, int order, float Sn_[]);
void find_aks(float Sn[], float a[], int Nsam, int order, float *E);
void weight(float ak[], float gamma, int order, float akw[]);
};
#endif

520
USRP2M17/codec2/nlp.cpp Normal file
View File

@ -0,0 +1,520 @@
/*---------------------------------------------------------------------------*\
FILE........: nlp.c
AUTHOR......: David Rowe
DATE CREATED: 23/3/93
Non Linear Pitch (NLP) estimation functions.
\*---------------------------------------------------------------------------*/
/*
Copyright (C) 2009 David Rowe
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include "defines.h"
#include "nlp.h"
#include "kiss_fft.h"
extern CKissFFT kiss;
/*---------------------------------------------------------------------------*\
GLOBALS
\*---------------------------------------------------------------------------*/
/* 48 tap 600Hz low pass FIR filter coefficients */
static const float nlp_fir[] =
{
-1.0818124e-03,
-1.1008344e-03,
-9.2768838e-04,
-4.2289438e-04,
5.5034190e-04,
2.0029849e-03,
3.7058509e-03,
5.1449415e-03,
5.5924666e-03,
4.3036754e-03,
8.0284511e-04,
-4.8204610e-03,
-1.1705810e-02,
-1.8199275e-02,
-2.2065282e-02,
-2.0920610e-02,
-1.2808831e-02,
3.2204775e-03,
2.6683811e-02,
5.5520624e-02,
8.6305944e-02,
1.1480192e-01,
1.3674206e-01,
1.4867556e-01,
1.4867556e-01,
1.3674206e-01,
1.1480192e-01,
8.6305944e-02,
5.5520624e-02,
2.6683811e-02,
3.2204775e-03,
-1.2808831e-02,
-2.0920610e-02,
-2.2065282e-02,
-1.8199275e-02,
-1.1705810e-02,
-4.8204610e-03,
8.0284511e-04,
4.3036754e-03,
5.5924666e-03,
5.1449415e-03,
3.7058509e-03,
2.0029849e-03,
5.5034190e-04,
-4.2289438e-04,
-9.2768838e-04,
-1.1008344e-03,
-1.0818124e-03
};
static const float fdmdv_os_filter[]= {
-0.0008215855034550382,
-0.0007833023901802921,
0.001075563790768233,
0.001199092367787555,
-0.001765309502928316,
-0.002055372115328064,
0.002986877604154257,
0.003462567920638414,
-0.004856570111126334,
-0.005563143845031497,
0.007533613299748122,
0.008563932468880897,
-0.01126857129039911,
-0.01280782411693687,
0.01651443896361847,
0.01894875110322284,
-0.02421604439474981,
-0.02845107338464062,
0.03672973563400258,
0.04542046150312214,
-0.06189165826716491,
-0.08721876380763803,
0.1496157094199961,
0.4497962274137046,
0.4497962274137046,
0.1496157094199961,
-0.08721876380763803,
-0.0618916582671649,
0.04542046150312216,
0.03672973563400257,
-0.02845107338464062,
-0.02421604439474984,
0.01894875110322284,
0.01651443896361848,
-0.01280782411693687,
-0.0112685712903991,
0.008563932468880899,
0.007533613299748123,
-0.005563143845031501,
-0.004856570111126346,
0.003462567920638419,
0.002986877604154259,
-0.002055372115328063,
-0.001765309502928318,
0.001199092367787557,
0.001075563790768233,
-0.0007833023901802925,
-0.0008215855034550383
};
/*---------------------------------------------------------------------------*\
nlp_create()
Initialisation function for NLP pitch estimator.
\*---------------------------------------------------------------------------*/
void Cnlp::nlp_create(C2CONST *c2const)
{
int i;
int m = c2const->m_pitch;
int Fs = c2const->Fs;
assert((Fs == 8000) || (Fs == 16000));
snlp.Fs = Fs;
snlp.m = m;
/* if running at 16kHz allocate storage for decimating filter memory */
if (Fs == 16000)
{
snlp.Sn16k.resize(FDMDV_OS_TAPS_16K + c2const->n_samp);
for(i=0; i<FDMDV_OS_TAPS_16K; i++)
{
snlp.Sn16k[i] = 0.0;
}
/* most processing occurs at 8 kHz sample rate so halve m */
m /= 2;
}
assert(m <= PMAX_M);
for(i=0; i<m/DEC; i++)
{
snlp.w[i] = 0.5 - 0.5*cosf(2*PI*i/(m/DEC-1));
}
for(i=0; i<PMAX_M; i++)
snlp.sq[i] = 0.0;
snlp.mem_x = 0.0;
snlp.mem_y = 0.0;
for(i=0; i<NLP_NTAP; i++)
snlp.mem_fir[i] = 0.0;
kiss.fft_alloc(snlp.fft_cfg, PE_FFT_SIZE, false);
}
/*---------------------------------------------------------------------------*\
nlp_destroy()
Shut down function for NLP pitch estimator.
\*---------------------------------------------------------------------------*/
void Cnlp::nlp_destroy()
{
snlp.fft_cfg.twiddles.clear();
}
/*---------------------------------------------------------------------------*\
nlp()
Determines the pitch in samples using the Non Linear Pitch (NLP)
algorithm [1]. Returns the fundamental in Hz. Note that the actual
pitch estimate is for the centre of the M sample Sn[] vector, not
the current N sample input vector. This is (I think) a delay of 2.5
frames with N=80 samples. You should align further analysis using
this pitch estimate to be centred on the middle of Sn[].
Two post processors have been tried, the MBE version (as discussed
in [1]), and a post processor that checks sub-multiples. Both
suffer occasional gross pitch errors (i.e. neither are perfect). In
the presence of background noise the sub-multiple algorithm tends
towards low F0 which leads to better sounding background noise than
the MBE post processor.
A good way to test and develop the NLP pitch estimator is using the
tnlp (codec2/unittest) and the codec2/octave/plnlp.m Octave script.
A pitch tracker searching a few frames forward and backward in time
would be a useful addition.
References:
[1] http://rowetel.com/downloads/1997_rowe_phd_thesis.pdf Chapter 4
\*---------------------------------------------------------------------------*/
float Cnlp::nlp(
float Sn[], /* input speech vector */
int n, /* frames shift (no. new samples in Sn[]) */
float *pitch, /* estimated pitch period in samples at current Fs */
// std::complex<float> Sw[], /* Freq domain version of Sn[] */
// float W[], /* Freq domain window */
float *prev_f0 /* previous pitch f0 in Hz, memory for pitch tracking */
)
{
float notch; /* current notch filter output */
std::complex<float> Fw[PE_FFT_SIZE]; /* DFT of squared signal (input/output) */
float gmax;
int gmax_bin;
int m, i, j;
float best_f0;
m = snlp.m;
/* Square, notch filter at DC, and LP filter vector */
/* If running at 16 kHz decimate to 8 kHz, as NLP ws designed for
Fs = 8kHz. The decimating filter introduces about 3ms of delay,
that shouldn't be a problem as pitch changes slowly. */
if (snlp.Fs == 8000)
{
/* Square latest input samples */
for(i=m-n; i<m; i++)
{
snlp.sq[i] = Sn[i]*Sn[i];
}
}
else
{
assert(snlp.Fs == 16000);
/* re-sample at 8 KHz */
for(i=0; i<n; i++)
{
snlp.Sn16k[FDMDV_OS_TAPS_16K+i] = Sn[m-n+i];
}
m /= 2;
n /= 2;
float Sn8k[n];
fdmdv_16_to_8(Sn8k, &snlp.Sn16k[FDMDV_OS_TAPS_16K], n);
/* Square latest input samples */
for(i=m-n, j=0; i<m; i++, j++)
{
snlp.sq[i] = Sn8k[j]*Sn8k[j];
}
assert(j <= n);
}
for(i=m-n; i<m; i++) /* notch filter at DC */
{
notch = snlp.sq[i] - snlp.mem_x;
notch += COEFF*snlp.mem_y;
snlp.mem_x = snlp.sq[i];
snlp.mem_y = notch;
snlp.sq[i] = notch + 1.0; /* With 0 input vectors to codec,
kiss_fft() would take a long
time to execute when running in
real time. Problem was traced
to kiss_fft function call in
this function. Adding this small
constant fixed problem. Not
exactly sure why. */
}
for(i=m-n; i<m; i++) /* FIR filter vector */
{
for(j=0; j<NLP_NTAP-1; j++)
snlp.mem_fir[j] = snlp.mem_fir[j+1];
snlp.mem_fir[NLP_NTAP-1] = snlp.sq[i];
snlp.sq[i] = 0.0;
for(j=0; j<NLP_NTAP; j++)
snlp.sq[i] += snlp.mem_fir[j]*nlp_fir[j];
}
/* Decimate and DFT */
for(i=0; i<PE_FFT_SIZE; i++)
{
Fw[i].real(0);
Fw[i].imag(0);
}
for(i=0; i<m/DEC; i++)
{
Fw[i].real(snlp.sq[i*DEC]*snlp.w[i]);
}
// FIXME: check if this can be converted to a real fft
// since all imag inputs are 0
codec2_fft_inplace(snlp.fft_cfg, Fw);
for(i=0; i<PE_FFT_SIZE; i++)
Fw[i].real(Fw[i].real() * Fw[i].real() + Fw[i].imag() * Fw[i].imag());
/* todo: express everything in f0, as pitch in samples is dep on Fs */
int pmin = floor(SAMPLE_RATE*P_MIN_S);
int pmax = floor(SAMPLE_RATE*P_MAX_S);
/* find global peak */
gmax = 0.0;
gmax_bin = PE_FFT_SIZE*DEC/pmax;
for(i=PE_FFT_SIZE*DEC/pmax; i<=PE_FFT_SIZE*DEC/pmin; i++)
{
if (Fw[i].real() > gmax)
{
gmax = Fw[i].real();
gmax_bin = i;
}
}
best_f0 = post_process_sub_multiples(Fw, pmax, gmax, gmax_bin, prev_f0);
/* Shift samples in buffer to make room for new samples */
for(i=0; i<m-n; i++)
snlp.sq[i] = snlp.sq[i+n];
/* return pitch period in samples and F0 estimate */
*pitch = (float)snlp.Fs/best_f0;
*prev_f0 = best_f0;
return(best_f0);
}
/*---------------------------------------------------------------------------*\
post_process_sub_multiples()
Given the global maximma of Fw[] we search integer submultiples for
local maxima. If local maxima exist and they are above an
experimentally derived threshold (OK a magic number I pulled out of
the air) we choose the submultiple as the F0 estimate.
The rational for this is that the lowest frequency peak of Fw[]
should be F0, as Fw[] can be considered the autocorrelation function
of Sw[] (the speech spectrum). However sometimes due to phase
effects the lowest frequency maxima may not be the global maxima.
This works OK in practice and favours low F0 values in the presence
of background noise which means the sinusoidal codec does an OK job
of synthesising the background noise. High F0 in background noise
tends to sound more periodic introducing annoying artifacts.
\*---------------------------------------------------------------------------*/
float Cnlp::post_process_sub_multiples(std::complex<float> Fw[], int pmax, float gmax, int gmax_bin, float *prev_f0)
{
int min_bin, cmax_bin;
int mult;
float thresh, best_f0;
int b, bmin, bmax, lmax_bin;
float lmax;
int prev_f0_bin;
/* post process estimate by searching submultiples */
mult = 2;
min_bin = PE_FFT_SIZE*DEC/pmax;
cmax_bin = gmax_bin;
prev_f0_bin = *prev_f0*(PE_FFT_SIZE*DEC)/SAMPLE_RATE;
while(gmax_bin/mult >= min_bin)
{
b = gmax_bin/mult; /* determine search interval */
bmin = 0.8*b;
bmax = 1.2*b;
if (bmin < min_bin)
bmin = min_bin;
/* lower threshold to favour previous frames pitch estimate,
this is a form of pitch tracking */
if ((prev_f0_bin > bmin) && (prev_f0_bin < bmax))
thresh = CNLP*0.5*gmax;
else
thresh = CNLP*gmax;
lmax = 0;
lmax_bin = bmin;
for (b=bmin; b<=bmax; b++) /* look for maximum in interval */
if (Fw[b].real() > lmax)
{
lmax = Fw[b].real();
lmax_bin = b;
}
if (lmax > thresh)
if ((lmax > Fw[lmax_bin-1].real()) && (lmax > Fw[lmax_bin+1].real()))
{
cmax_bin = lmax_bin;
}
mult++;
}
best_f0 = (float)cmax_bin*SAMPLE_RATE/(PE_FFT_SIZE*DEC);
return best_f0;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: fdmdv_16_to_8()
AUTHOR......: David Rowe
DATE CREATED: 9 May 2012
Changes the sample rate of a signal from 16 to 8 kHz.
n is the number of samples at the 8 kHz rate, there are FDMDV_OS*n
samples at the 48 kHz rate. As above however a memory of
FDMDV_OS_TAPS samples is reqd for in16k[] (see t16_8.c unit test as example).
Low pass filter the 16 kHz signal at 4 kHz using the same filter as
the upsampler, then just output every FDMDV_OS-th filtered sample.
Note: this function copied from fdmdv.c, included in nlp.c as a convenience
to avoid linking with another source file.
\*---------------------------------------------------------------------------*/
void Cnlp::fdmdv_16_to_8(float out8k[], float in16k[], int n)
{
float acc;
int i,j,k;
for(i=0, k=0; k<n; i+=FDMDV_OS, k++)
{
acc = 0.0;
for(j=0; j<FDMDV_OS_TAPS_16K; j++)
acc += fdmdv_os_filter[j]*in16k[i-j];
out8k[k] = acc;
}
/* update filter memory */
for(i=-FDMDV_OS_TAPS_16K; i<0; i++)
in16k[i] = in16k[i + n*FDMDV_OS];
}
// there is a little overhead for inplace kiss_fft but this is
// on the powerful platforms like the Raspberry or even x86 PC based ones
// not noticeable
// the reduced usage of RAM and increased performance on STM32 platforms
// should be worth it.
void Cnlp::codec2_fft_inplace(FFT_STATE &cfg, std::complex<float> *inout)
{
std::complex<float> in[512];
// decide whether to use the local stack based buffer for in
// or to allow kiss_fft to allocate RAM
// second part is just to play safe since first method
// is much faster and uses less RAM
if (cfg.nfft <= 512)
{
memcpy(in, inout, cfg.nfft*sizeof(std::complex<float>));
kiss.fft(cfg, in, inout);
}
else
{
kiss.fft(cfg, inout, inout);
}
}

87
USRP2M17/codec2/nlp.h Normal file
View File

@ -0,0 +1,87 @@
/*---------------------------------------------------------------------------*\
FILE........: nlp.c
AUTHOR......: David Rowe
DATE CREATED: 23/3/93
Non Linear Pitch (NLP) estimation functions.
\*---------------------------------------------------------------------------*/
/*
Copyright (C) 2009 David Rowe
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __NLP__
#define __NLP__
#include <complex>
#include <vector>
#include "defines.h"
/*---------------------------------------------------------------------------*\
DEFINES
\*---------------------------------------------------------------------------*/
#define PMAX_M 320 /* maximum NLP analysis window size */
#define COEFF 0.95 /* notch filter parameter */
#define PE_FFT_SIZE 512 /* DFT size for pitch estimation */
#define DEC 5 /* decimation factor */
#define SAMPLE_RATE 8000
#define PI 3.141592654 /* mathematical constant */
//#define T 0.1 /* threshold for local minima candidate */
#define F0_MAX 500
#define CNLP 0.3 /* post processor constant */
#define NLP_NTAP 48 /* Decimation LPF order */
/* 8 to 16 kHz sample rate conversion */
#define FDMDV_OS 2 /* oversampling rate */
#define FDMDV_OS_TAPS_16K 48 /* number of OS filter taps at 16kHz */
#define FDMDV_OS_TAPS_8K (FDMDV_OS_TAPS_16K/FDMDV_OS) /* number of OS filter taps at 8kHz */
using NLP = struct nlp_tag
{
int Fs; /* sample rate in Hz */
int m;
float w[PMAX_M/DEC]; /* DFT window */
float sq[PMAX_M]; /* squared speech samples */
float mem_x,mem_y; /* memory for notch filter */
float mem_fir[NLP_NTAP]; /* decimation FIR filter memory */
FFT_STATE fft_cfg; /* kiss FFT config */
std::vector<float> Sn16k; /* Fs=16kHz input speech vector */
};
class Cnlp {
public:
void nlp_create(C2CONST *c2const);
void nlp_destroy();
float nlp(float Sn[], int n, float *pitch_samples, float *prev_f0);
void codec2_fft_inplace(FFT_STATE &cfg, std::complex<float> *inout);
private:
float post_process_sub_multiples(std::complex<float> Fw[], int pmax, float gmax, int gmax_bin, float *prev_f0);
void fdmdv_16_to_8(float out8k[], float in16k[], int n);
NLP snlp;
};
#endif

139
USRP2M17/codec2/pack.cpp Normal file
View File

@ -0,0 +1,139 @@
/*
Copyright (C) 2010 Perens LLC <bruce@perens.com>
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "defines.h"
#include "quantise.h"
#include <stdio.h>
/* Compile-time constants */
/* Size of unsigned char in bits. Assumes 8 bits-per-char. */
static const unsigned int WordSize = 8;
/* Mask to pick the bit component out of bitIndex. */
static const unsigned int IndexMask = 0x7;
/* Used to pick the word component out of bitIndex. */
static const unsigned int ShiftRight = 3;
/** Pack a bit field into a bit string, encoding the field in Gray code.
*
* The output is an array of unsigned char data. The fields are efficiently
* packed into the bit string. The Gray coding is a naive attempt to reduce
* the effect of single-bit errors, we expect to do a better job as the
* codec develops.
*
* This code would be simpler if it just set one bit at a time in the string,
* but would hit the same cache line more often. I'm not sure the complexity
* gains us anything here.
*
* Although field is currently of int type rather than unsigned for
* compatibility with the rest of the code, indices are always expected to
* be >= 0.
*/
void CQuantize::pack(
unsigned char *bitArray, /* The output bit string. */
unsigned int *bitIndex, /* Index into the string in BITS, not bytes.*/
int field, /* The bit field to be packed. */
unsigned int fieldWidth /* Width of the field in BITS, not bytes. */
)
{
pack_natural_or_gray(bitArray, bitIndex, field, fieldWidth, 1);
}
void CQuantize::pack_natural_or_gray(
unsigned char *bitArray, /* The output bit string. */
unsigned int *bitIndex, /* Index into the string in BITS, not bytes.*/
int field, /* The bit field to be packed. */
unsigned int fieldWidth, /* Width of the field in BITS, not bytes. */
unsigned int gray /* non-zero for gray coding */
)
{
if (gray)
{
/* Convert the field to Gray code */
field = (field >> 1) ^ field;
}
do
{
unsigned int bI = *bitIndex;
unsigned int bitsLeft = WordSize - (bI & IndexMask);
unsigned int sliceWidth = bitsLeft < fieldWidth ? bitsLeft : fieldWidth;
unsigned int wordIndex = bI >> ShiftRight;
bitArray[wordIndex] |= ((unsigned char)((field >> (fieldWidth - sliceWidth)) << (bitsLeft - sliceWidth)));
*bitIndex = bI + sliceWidth;
fieldWidth -= sliceWidth;
}
while ( fieldWidth != 0 );
}
/** Unpack a field from a bit string, converting from Gray code to binary.
*
*/
int CQuantize::unpack(
const unsigned char *bitArray, /* The input bit string. */
unsigned int *bitIndex, /* Index into the string in BITS, not bytes.*/
unsigned int fieldWidth/* Width of the field in BITS, not bytes. */
)
{
return unpack_natural_or_gray(bitArray, bitIndex, fieldWidth, 1);
}
/** Unpack a field from a bit string, to binary, optionally using
* natural or Gray code.
*
*/
int CQuantize::unpack_natural_or_gray(
const unsigned char *bitArray, /* The input bit string. */
unsigned int *bitIndex, /* Index into the string in BITS, not bytes.*/
unsigned int fieldWidth,/* Width of the field in BITS, not bytes. */
unsigned int gray /* non-zero for Gray coding */
)
{
unsigned int field = 0;
unsigned int t;
do
{
unsigned int bI = *bitIndex;
unsigned int bitsLeft = WordSize - (bI & IndexMask);
unsigned int sliceWidth = bitsLeft < fieldWidth ? bitsLeft : fieldWidth;
field |= (((bitArray[bI >> ShiftRight] >> (bitsLeft - sliceWidth)) & ((1 << sliceWidth) - 1)) << (fieldWidth - sliceWidth));
*bitIndex = bI + sliceWidth;
fieldWidth -= sliceWidth;
}
while ( fieldWidth != 0 );
if (gray)
{
/* Convert from Gray code to binary. Works for maximum 8-bit fields. */
t = field ^ (field >> 8);
t ^= (t >> 4);
t ^= (t >> 2);
t ^= (t >> 1);
}
else
{
t = field;
}
return t;
}

247
USRP2M17/codec2/qbase.cpp Normal file
View File

@ -0,0 +1,247 @@
#include <assert.h>
#include <math.h>
#include "qbase.h"
/*---------------------------------------------------------------------------*\
quantise
Quantises vec by choosing the nearest vector in codebook cb, and
returns the vector index. The squared error of the quantised vector
is added to se.
\*---------------------------------------------------------------------------*/
long CQbase::quantise(const float *cb, float vec[], float w[], int k, int m, float *se)
/* float cb[][K]; current VQ codebook */
/* float vec[]; vector to quantise */
/* float w[]; weighting vector */
/* int k; dimension of vectors */
/* int m; size of codebook */
/* float *se; accumulated squared error */
{
float e; /* current error */
long besti; /* best index so far */
float beste; /* best error so far */
long j;
int i;
float diff;
besti = 0;
beste = 1E32;
for(j=0; j<m; j++)
{
e = 0.0;
for(i=0; i<k; i++)
{
diff = cb[j*k+i]-vec[i];
e += (diff*w[i] * diff*w[i]);
}
if (e < beste)
{
beste = e;
besti = j;
}
}
*se += beste;
return(besti);
}
/*---------------------------------------------------------------------------*\
FUNCTION....: encode_WoE()
AUTHOR......: Jean-Marc Valin & David Rowe
DATE CREATED: 11 May 2012
Joint Wo and LPC energy vector quantiser developed my Jean-Marc
Valin. Returns index, and updated states xq[].
\*---------------------------------------------------------------------------*/
int CQbase::encode_WoE(MODEL *model, float e, float xq[])
{
int i, n1;
float x[2];
float err[2];
float w[2];
const float *codebook1 = ge_cb[0].cb;
int nb_entries = ge_cb[0].m;
int ndim = ge_cb[0].k;
assert((1<<WO_E_BITS) == nb_entries);
if (e < 0.0) e = 0; /* occasional small negative energies due LPC round off I guess */
x[0] = log10f((model->Wo/PI)*4000.0/50.0)/log10f(2);
x[1] = 10.0*log10f(1e-4 + e);
compute_weights2(x, xq, w);
for (i=0; i<ndim; i++)
err[i] = x[i]-ge_coeff[i]*xq[i];
n1 = find_nearest_weighted(codebook1, nb_entries, err, w, ndim);
for (i=0; i<ndim; i++)
{
xq[i] = ge_coeff[i]*xq[i] + codebook1[ndim*n1+i];
err[i] -= codebook1[ndim*n1+i];
}
//printf("enc: %f %f (%f)(%f) \n", xq[0], xq[1], e, 10.0*log10(1e-4 + e));
return n1;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: decode_WoE()
AUTHOR......: Jean-Marc Valin & David Rowe
DATE CREATED: 11 May 2012
Joint Wo and LPC energy vector quantiser developed my Jean-Marc
Valin. Given index and states xq[], returns Wo & E, and updates
states xq[].
\*---------------------------------------------------------------------------*/
void CQbase::decode_WoE(C2CONST *c2const, MODEL *model, float *e, float xq[], int n1)
{
int i;
const float *codebook1 = ge_cb[0].cb;
int ndim = ge_cb[0].k;
float Wo_min = c2const->Wo_min;
float Wo_max = c2const->Wo_max;
for (i=0; i<ndim; i++)
{
xq[i] = ge_coeff[i]*xq[i] + codebook1[ndim*n1+i];
}
//printf("dec: %f %f\n", xq[0], xq[1]);
model->Wo = powf(2.0, xq[0])*(PI*50.0)/4000.0;
/* bit errors can make us go out of range leading to all sorts of
probs like seg faults */
if (model->Wo > Wo_max) model->Wo = Wo_max;
if (model->Wo < Wo_min) model->Wo = Wo_min;
model->L = PI/model->Wo; /* if we quantise Wo re-compute L */
*e = exp10f(xq[1]/10.0);
}
void CQbase::compute_weights2(const float *x, const float *xp, float *w)
{
w[0] = 30;
w[1] = 1;
if (x[1]<0)
{
w[0] *= .6;
w[1] *= .3;
}
if (x[1]<-10)
{
w[0] *= .3;
w[1] *= .3;
}
/* Higher weight if pitch is stable */
if (fabsf(x[0]-xp[0])<.2)
{
w[0] *= 2;
w[1] *= 1.5;
}
else if (fabsf(x[0]-xp[0])>.5) /* Lower if not stable */
{
w[0] *= .5;
}
/* Lower weight for low energy */
if (x[1] < xp[1]-10)
{
w[1] *= .5;
}
if (x[1] < xp[1]-20)
{
w[1] *= .5;
}
//w[0] = 30;
//w[1] = 1;
/* Square the weights because it's applied on the squared error */
w[0] *= w[0];
w[1] *= w[1];
}
int CQbase::find_nearest_weighted(const float *codebook, int nb_entries, float *x, const float *w, int ndim)
{
int i, j;
float min_dist = 1e15;
int nearest = 0;
for (i=0; i<nb_entries; i++)
{
float dist=0;
for (j=0; j<ndim; j++)
dist += w[j]*(x[j]-codebook[i*ndim+j])*(x[j]-codebook[i*ndim+j]);
if (dist<min_dist)
{
min_dist = dist;
nearest = i;
}
}
return nearest;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: encode_log_Wo()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Encodes Wo in the log domain using a WO_LEVELS quantiser.
\*---------------------------------------------------------------------------*/
int CQbase::encode_log_Wo(C2CONST *c2const, float Wo, int bits)
{
int index, Wo_levels = 1<<bits;
float Wo_min = c2const->Wo_min;
float Wo_max = c2const->Wo_max;
float norm;
norm = (log10f(Wo) - log10f(Wo_min))/(log10f(Wo_max) - log10f(Wo_min));
index = floorf(Wo_levels * norm + 0.5);
if (index < 0 ) index = 0;
if (index > (Wo_levels-1)) index = Wo_levels-1;
return index;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: decode_log_Wo()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Decodes Wo using a WO_LEVELS quantiser in the log domain.
\*---------------------------------------------------------------------------*/
float CQbase::decode_log_Wo(C2CONST *c2const, int index, int bits)
{
float Wo_min = c2const->Wo_min;
float Wo_max = c2const->Wo_max;
float step;
float Wo;
int Wo_levels = 1<<bits;
step = (log10f(Wo_max) - log10f(Wo_min))/Wo_levels;
Wo = log10f(Wo_min) + step*(index);
return exp10f(Wo);
}

39
USRP2M17/codec2/qbase.h Normal file
View File

@ -0,0 +1,39 @@
#ifndef QBASE_H
#define QBASE_H
#include "defines.h"
#define WO_BITS 7
#define WO_LEVELS (1<<WO_BITS)
#define WO_DT_BITS 3
#define E_BITS 5
#define E_LEVELS (1<<E_BITS)
#define E_MIN_DB -10.0
#define E_MAX_DB 40.0
#define LSP_SCALAR_INDEXES 10
#define LSPD_SCALAR_INDEXES 10
#define LSP_PRED_VQ_INDEXES 3
#define WO_E_BITS 8
#define LPCPF_GAMMA 0.5
#define LPCPF_BETA 0.2
class CQbase {
public:
int encode_WoE(MODEL *model, float e, float xq[]);
void decode_WoE(C2CONST *c2const, MODEL *model, float *e, float xq[], int n1);
int encode_log_Wo(C2CONST *c2const, float Wo, int bits);
float decode_log_Wo(C2CONST *c2const, int index, int bits);
protected:
long quantise(const float * cb, float vec[], float w[], int k, int m, float *se);
void compute_weights2(const float *x, const float *xp, float *w);
int find_nearest_weighted(const float *codebook, int nb_entries, float *x, const float *w, int ndim);
const float ge_coeff[2] = { 0.8, 0.9 };
};
#endif

View File

@ -0,0 +1,897 @@
/*---------------------------------------------------------------------------*\
FILE........: quantise.c
AUTHOR......: David Rowe
DATE CREATED: 31/5/92
Quantisation functions for the sinusoidal coder.
\*---------------------------------------------------------------------------*/
/*
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "defines.h"
#include "quantise.h"
#include "lpc.h"
#include "kiss_fft.h"
extern CKissFFT kiss;
#define LSP_DELTA1 0.01 /* grid spacing for LSP root searches */
/*---------------------------------------------------------------------------*\
FUNCTIONS
\*---------------------------------------------------------------------------*/
int CQuantize::lsp_bits(int i)
{
return lsp_cb[i].log2m;
}
int CQuantize::lspd_bits(int i)
{
return lsp_cbd[i].log2m;
}
/*---------------------------------------------------------------------------*\
encode_lspds_scalar()
Scalar/VQ LSP difference quantiser.
\*---------------------------------------------------------------------------*/
void CQuantize::encode_lspds_scalar(int indexes[], float lsp[], int order)
{
int i,k,m;
float lsp_hz[order];
float lsp__hz[order];
float dlsp[order];
float dlsp_[order];
float wt[order];
const float *cb;
float se;
for(i=0; i<order; i++)
{
wt[i] = 1.0;
}
/* convert from radians to Hz so we can use human readable
frequencies */
for(i=0; i<order; i++)
lsp_hz[i] = (4000.0/PI)*lsp[i];
wt[0] = 1.0;
for(i=0; i<order; i++)
{
/* find difference from previous qunatised lsp */
if (i)
dlsp[i] = lsp_hz[i] - lsp__hz[i-1];
else
dlsp[0] = lsp_hz[0];
k = lsp_cbd[i].k;
m = lsp_cbd[i].m;
cb = lsp_cbd[i].cb;
indexes[i] = quantise(cb, &dlsp[i], wt, k, m, &se);
dlsp_[i] = cb[indexes[i]*k];
if (i)
lsp__hz[i] = lsp__hz[i-1] + dlsp_[i];
else
lsp__hz[0] = dlsp_[0];
}
}
void CQuantize::decode_lspds_scalar( float lsp_[], int indexes[], int order)
{
int i,k;
float lsp__hz[order];
float dlsp_[order];
const float *cb;
for(i=0; i<order; i++)
{
k = lsp_cbd[i].k;
cb = lsp_cbd[i].cb;
dlsp_[i] = cb[indexes[i]*k];
if (i)
lsp__hz[i] = lsp__hz[i-1] + dlsp_[i];
else
lsp__hz[0] = dlsp_[0];
lsp_[i] = (PI/4000.0)*lsp__hz[i];
}
}
#define MIN(a,b) ((a)<(b)?(a):(b))
#define MAX_ENTRIES 16384
void CQuantize::compute_weights(const float *x, float *w, int ndim)
{
int i;
w[0] = MIN(x[0], x[1]-x[0]);
for (i=1; i<ndim-1; i++)
w[i] = MIN(x[i]-x[i-1], x[i+1]-x[i]);
w[ndim-1] = MIN(x[ndim-1]-x[ndim-2], PI-x[ndim-1]);
for (i=0; i<ndim; i++)
w[i] = 1./(.01+w[i]);
}
int CQuantize::find_nearest(const float *codebook, int nb_entries, float *x, int ndim)
{
int i, j;
float min_dist = 1e15;
int nearest = 0;
for (i=0; i<nb_entries; i++)
{
float dist=0;
for (j=0; j<ndim; j++)
dist += (x[j]-codebook[i*ndim+j])*(x[j]-codebook[i*ndim+j]);
if (dist<min_dist)
{
min_dist = dist;
nearest = i;
}
}
return nearest;
}
int CQuantize::check_lsp_order(float lsp[], int order)
{
int i;
float tmp;
int swaps = 0;
for(i=1; i<order; i++)
if (lsp[i] < lsp[i-1])
{
//fprintf(stderr, "swap %d\n",i);
swaps++;
tmp = lsp[i-1];
lsp[i-1] = lsp[i]-0.1;
lsp[i] = tmp+0.1;
i = 1; /* start check again, as swap may have caused out of order */
}
return swaps;
}
/*---------------------------------------------------------------------------*\
lpc_post_filter()
Applies a post filter to the LPC synthesis filter power spectrum
Pw, which supresses the inter-formant energy.
The algorithm is from p267 (Section 8.6) of "Digital Speech",
edited by A.M. Kondoz, 1994 published by Wiley and Sons. Chapter 8
of this text is on the MBE vocoder, and this is a freq domain
adaptation of post filtering commonly used in CELP.
I used the Octave simulation lpcpf.m to get an understanding of the
algorithm.
Requires two more FFTs which is significantly more MIPs. However
it should be possible to implement this more efficiently in the
time domain. Just not sure how to handle relative time delays
between the synthesis stage and updating these coeffs. A smaller
FFT size might also be accetable to save CPU.
TODO:
[ ] sync var names between Octave and C version
[ ] doc gain normalisation
[ ] I think the first FFT is not rqd as we do the same
thing in aks_to_M2().
\*---------------------------------------------------------------------------*/
void CQuantize::lpc_post_filter(FFTR_STATE *fftr_fwd_cfg, float Pw[], float ak[], int order, float beta, float gamma, int bass_boost, float E)
{
int i;
float x[FFT_ENC]; /* input to FFTs */
std::complex<float> Ww[FFT_ENC/2+1]; /* weighting spectrum */
float Rw[FFT_ENC/2+1]; /* R = WA */
float e_before, e_after, gain;
float Pfw;
float max_Rw, min_Rw;
float coeff;
/* Determine weighting filter spectrum W(exp(jw)) ---------------*/
for(i=0; i<FFT_ENC; i++)
{
x[i] = 0.0;
}
x[0] = ak[0];
coeff = gamma;
for(i=1; i<=order; i++)
{
x[i] = ak[i] * coeff;
coeff *= gamma;
}
kiss.fftr(*fftr_fwd_cfg, x, Ww);
for(i=0; i<FFT_ENC/2; i++)
{
Ww[i].real(Ww[i].real() * Ww[i].real() + Ww[i].imag() * Ww[i].imag());
}
/* Determined combined filter R = WA ---------------------------*/
max_Rw = 0.0;
min_Rw = 1E32;
for(i=0; i<FFT_ENC/2; i++)
{
Rw[i] = sqrtf(Ww[i].real() * Pw[i]);
if (Rw[i] > max_Rw)
max_Rw = Rw[i];
if (Rw[i] < min_Rw)
min_Rw = Rw[i];
}
/* create post filter mag spectrum and apply ------------------*/
/* measure energy before post filtering */
e_before = 1E-4;
for(i=0; i<FFT_ENC/2; i++)
e_before += Pw[i];
/* apply post filter and measure energy */
e_after = 1E-4;
for(i=0; i<FFT_ENC/2; i++)
{
Pfw = powf(Rw[i], beta);
Pw[i] *= Pfw * Pfw;
e_after += Pw[i];
}
gain = e_before/e_after;
/* apply gain factor to normalise energy, and LPC Energy */
gain *= E;
for(i=0; i<FFT_ENC/2; i++)
{
Pw[i] *= gain;
}
if (bass_boost)
{
/* add 3dB to first 1 kHz to account for LP effect of PF */
for(i=0; i<FFT_ENC/8; i++)
{
Pw[i] *= 1.4*1.4;
}
}
}
/*---------------------------------------------------------------------------*\
aks_to_M2()
Transforms the linear prediction coefficients to spectral amplitude
samples. This function determines A(m) from the average energy per
band using an FFT.
\*---------------------------------------------------------------------------*/
void CQuantize::aks_to_M2(
FFTR_STATE * fftr_fwd_cfg,
float ak[], /* LPC's */
int order,
MODEL *model, /* sinusoidal model parameters for this frame */
float E, /* energy term */
float *snr, /* signal to noise ratio for this frame in dB */
int sim_pf, /* true to simulate a post filter */
int pf, /* true to enable actual LPC post filter */
int bass_boost, /* enable LPC filter 0-1kHz 3dB boost */
float beta,
float gamma, /* LPC post filter parameters */
std::complex<float> Aw[] /* output power spectrum */
)
{
int i,m; /* loop variables */
int am,bm; /* limits of current band */
float r; /* no. rads/bin */
float Em; /* energy in band */
float Am; /* spectral amplitude sample */
float signal, noise;
r = TWO_PI/(FFT_ENC);
/* Determine DFT of A(exp(jw)) --------------------------------------------*/
{
float a[FFT_ENC]; /* input to FFT for power spectrum */
for(i=0; i<FFT_ENC; i++)
{
a[i] = 0.0;
}
for(i=0; i<=order; i++)
a[i] = ak[i];
kiss.fftr(*fftr_fwd_cfg, a, Aw);
}
/* Determine power spectrum P(w) = E/(A(exp(jw))^2 ------------------------*/
float Pw[FFT_ENC/2];
for(i=0; i<FFT_ENC/2; i++)
{
Pw[i] = 1.0/(Aw[i].real() * Aw[i].real() + Aw[i].imag() * Aw[i].imag() + 1E-6);
}
if (pf)
lpc_post_filter(fftr_fwd_cfg, Pw, ak, order, beta, gamma, bass_boost, E);
else
{
for(i=0; i<FFT_ENC/2; i++)
{
Pw[i] *= E;
}
}
/* Determine magnitudes from P(w) ----------------------------------------*/
/* when used just by decoder {A} might be all zeroes so init signal
and noise to prevent log(0) errors */
signal = 1E-30;
noise = 1E-32;
for(m=1; m<=model->L; m++)
{
am = (int)((m - 0.5)*model->Wo/r + 0.5);
bm = (int)((m + 0.5)*model->Wo/r + 0.5);
// FIXME: With arm_rfft_fast_f32 we have to use this
// otherwise sometimes a to high bm is calculated
// which causes trouble later in the calculation
// chain
// it seems for some reason model->Wo is calculated somewhat too high
if (bm>FFT_ENC/2)
{
bm = FFT_ENC/2;
}
Em = 0.0;
for(i=am; i<bm; i++)
Em += Pw[i];
Am = sqrtf(Em);
signal += model->A[m]*model->A[m];
noise += (model->A[m] - Am)*(model->A[m] - Am);
/* This code significantly improves perf of LPC model, in
particular when combined with phase0. The LPC spectrum tends
to track just under the peaks of the spectral envelope, and
just above nulls. This algorithm does the reverse to
compensate - raising the amplitudes of spectral peaks, while
attenuating the null. This enhances the formants, and
supresses the energy between formants. */
if (sim_pf)
{
if (Am > model->A[m])
Am *= 0.7;
if (Am < model->A[m])
Am *= 1.4;
}
model->A[m] = Am;
}
*snr = 10.0*log10f(signal/noise);
}
/*---------------------------------------------------------------------------*\
FUNCTION....: encode_Wo()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Encodes Wo using a WO_LEVELS quantiser.
\*---------------------------------------------------------------------------*/
int CQuantize::encode_Wo(C2CONST *c2const, float Wo, int bits)
{
int index, Wo_levels = 1<<bits;
float Wo_min = c2const->Wo_min;
float Wo_max = c2const->Wo_max;
float norm;
norm = (Wo - Wo_min)/(Wo_max - Wo_min);
index = floorf(Wo_levels * norm + 0.5);
if (index < 0 ) index = 0;
if (index > (Wo_levels-1)) index = Wo_levels-1;
return index;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: decode_Wo()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Decodes Wo using a WO_LEVELS quantiser.
\*---------------------------------------------------------------------------*/
float CQuantize::decode_Wo(C2CONST *c2const, int index, int bits)
{
float Wo_min = c2const->Wo_min;
float Wo_max = c2const->Wo_max;
float step;
float Wo;
int Wo_levels = 1<<bits;
step = (Wo_max - Wo_min)/Wo_levels;
Wo = Wo_min + step*(index);
return Wo;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: speech_to_uq_lsps()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Analyse a windowed frame of time domain speech to determine LPCs
which are the converted to LSPs for quantisation and transmission
over the channel.
\*---------------------------------------------------------------------------*/
float CQuantize::speech_to_uq_lsps(float lsp[], float ak[], float Sn[], float w[], int m_pitch, int order)
{
int i, roots;
float Wn[m_pitch];
float R[order+1];
float e, E;
Clpc lpc;
e = 0.0;
for(i=0; i<m_pitch; i++)
{
Wn[i] = Sn[i]*w[i];
e += Wn[i]*Wn[i];
}
/* trap 0 energy case as LPC analysis will fail */
if (e == 0.0)
{
for(i=0; i<order; i++)
lsp[i] = (PI/order)*(float)i;
return 0.0;
}
lpc.autocorrelate(Wn, R, m_pitch, order);
lpc.levinson_durbin(R, ak, order);
E = 0.0;
for(i=0; i<=order; i++)
E += ak[i]*R[i];
/* 15 Hz BW expansion as I can't hear the difference and it may help
help occasional fails in the LSP root finding. Important to do this
after energy calculation to avoid -ve energy values.
*/
for(i=0; i<=order; i++)
ak[i] *= powf(0.994,(float)i);
roots = lpc_to_lsp(ak, order, lsp, 5, LSP_DELTA1);
if (roots != order)
{
/* if root finding fails use some benign LSP values instead */
for(i=0; i<order; i++)
lsp[i] = (PI/order)*(float)i;
}
return E;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: encode_lsps_scalar()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Thirty-six bit sclar LSP quantiser. From a vector of unquantised
(floating point) LSPs finds the quantised LSP indexes.
\*---------------------------------------------------------------------------*/
void CQuantize::encode_lsps_scalar(int indexes[], float lsp[], int order)
{
int i,k,m;
float wt[1];
float lsp_hz[order];
const float *cb;
float se;
/* convert from radians to Hz so we can use human readable
frequencies */
for(i=0; i<order; i++)
lsp_hz[i] = (4000.0/PI)*lsp[i];
/* scalar quantisers */
wt[0] = 1.0;
for(i=0; i<order; i++)
{
k = lsp_cb[i].k;
m = lsp_cb[i].m;
cb = lsp_cb[i].cb;
indexes[i] = quantise(cb, &lsp_hz[i], wt, k, m, &se);
}
}
/*---------------------------------------------------------------------------*\
FUNCTION....: decode_lsps_scalar()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
From a vector of quantised LSP indexes, returns the quantised
(floating point) LSPs.
\*---------------------------------------------------------------------------*/
void CQuantize::decode_lsps_scalar(float lsp[], int indexes[], int order)
{
int i,k;
float lsp_hz[order];
const float *cb;
for(i=0; i<order; i++)
{
k = lsp_cb[i].k;
cb = lsp_cb[i].cb;
lsp_hz[i] = cb[indexes[i]*k];
}
/* convert back to radians */
for(i=0; i<order; i++)
lsp[i] = (PI/4000.0)*lsp_hz[i];
}
/*---------------------------------------------------------------------------*\
FUNCTION....: bw_expand_lsps()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Applies Bandwidth Expansion (BW) to a vector of LSPs. Prevents any
two LSPs getting too close together after quantisation. We know
from experiment that LSP quantisation errors < 12.5Hz (25Hz step
size) are inaudible so we use that as the minimum LSP separation.
\*---------------------------------------------------------------------------*/
void CQuantize::bw_expand_lsps(float lsp[], int order, float min_sep_low, float min_sep_high)
{
int i;
for(i=1; i<4; i++)
{
if ((lsp[i] - lsp[i-1]) < min_sep_low*(PI/4000.0))
lsp[i] = lsp[i-1] + min_sep_low*(PI/4000.0);
}
/* As quantiser gaps increased, larger BW expansion was required
to prevent twinkly noises. This may need more experiment for
different quanstisers.
*/
for(i=4; i<order; i++)
{
if (lsp[i] - lsp[i-1] < min_sep_high*(PI/4000.0))
lsp[i] = lsp[i-1] + min_sep_high*(PI/4000.0);
}
}
/*---------------------------------------------------------------------------*\
FUNCTION....: apply_lpc_correction()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Apply first harmonic LPC correction at decoder. This helps improve
low pitch males after LPC modelling, like hts1a and morig.
\*---------------------------------------------------------------------------*/
void CQuantize::apply_lpc_correction(MODEL *model)
{
if (model->Wo < (PI*150.0/4000))
{
model->A[1] *= 0.032;
}
}
/*---------------------------------------------------------------------------*\
FUNCTION....: encode_energy()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Encodes LPC energy using an E_LEVELS quantiser.
\*---------------------------------------------------------------------------*/
int CQuantize::encode_energy(float e, int bits)
{
int index, e_levels = 1<<bits;
float e_min = E_MIN_DB;
float e_max = E_MAX_DB;
float norm;
e = 10.0*log10f(e);
norm = (e - e_min)/(e_max - e_min);
index = floorf(e_levels * norm + 0.5);
if (index < 0 ) index = 0;
if (index > (e_levels-1)) index = e_levels-1;
return index;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: decode_energy()
AUTHOR......: David Rowe
DATE CREATED: 22/8/2010
Decodes energy using a E_LEVELS quantiser.
\*---------------------------------------------------------------------------*/
float CQuantize::decode_energy(int index, int bits)
{
float e_min = E_MIN_DB;
float e_max = E_MAX_DB;
float step;
float e;
int e_levels = 1<<bits;
step = (e_max - e_min)/e_levels;
e = e_min + step*(index);
e = exp10f(e/10.0);
return e;
}
/*---------------------------------------------------------------------------*\
FUNCTION....: lpc_to_lsp()
AUTHOR......: David Rowe
DATE CREATED: 24/2/93
This function converts LPC coefficients to LSP coefficients.
\*---------------------------------------------------------------------------*/
int CQuantize::lpc_to_lsp(float *a, int order, float *freq, int nb, float delta)
/* float *a lpc coefficients */
/* int order order of LPC coefficients (10) */
/* float *freq LSP frequencies in radians */
/* int nb number of sub-intervals (4) */
/* float delta grid spacing interval (0.02) */
{
float psuml,psumr,psumm,temp_xr,xl,xr,xm = 0;
float temp_psumr;
int i,j,m,flag,k;
float *px; /* ptrs of respective P'(z) & Q'(z) */
float *qx;
float *p;
float *q;
float *pt; /* ptr used for cheb_poly_eval()
whether P' or Q' */
int roots=0; /* number of roots found */
float Q[order + 1];
float P[order + 1];
flag = 1;
m = order/2; /* order of P'(z) & Q'(z) polynimials */
/* Allocate memory space for polynomials */
/* determine P'(z)'s and Q'(z)'s coefficients where
P'(z) = P(z)/(1 + z^(-1)) and Q'(z) = Q(z)/(1-z^(-1)) */
px = P; /* initilaise ptrs */
qx = Q;
p = px;
q = qx;
*px++ = 1.0;
*qx++ = 1.0;
for(i=1; i<=m; i++)
{
*px++ = a[i]+a[order+1-i]-*p++;
*qx++ = a[i]-a[order+1-i]+*q++;
}
px = P;
qx = Q;
for(i=0; i<m; i++)
{
*px = 2**px;
*qx = 2**qx;
px++;
qx++;
}
px = P; /* re-initialise ptrs */
qx = Q;
/* Search for a zero in P'(z) polynomial first and then alternate to Q'(z).
Keep alternating between the two polynomials as each zero is found */
xr = 0; /* initialise xr to zero */
xl = 1.0; /* start at point xl = 1 */
for(j=0; j<order; j++)
{
if(j%2) /* determines whether P' or Q' is eval. */
pt = qx;
else
pt = px;
psuml = cheb_poly_eva(pt,xl,order); /* evals poly. at xl */
flag = 1;
while(flag && (xr >= -1.0))
{
xr = xl - delta ; /* interval spacing */
psumr = cheb_poly_eva(pt,xr,order);/* poly(xl-delta_x) */
temp_psumr = psumr;
temp_xr = xr;
/* if no sign change increment xr and re-evaluate
poly(xr). Repeat til sign change. if a sign change has
occurred the interval is bisected and then checked again
for a sign change which determines in which interval the
zero lies in. If there is no sign change between poly(xm)
and poly(xl) set interval between xm and xr else set
interval between xl and xr and repeat till root is located
within the specified limits */
if(((psumr*psuml)<0.0) || (psumr == 0.0))
{
roots++;
psumm=psuml;
for(k=0; k<=nb; k++)
{
xm = (xl+xr)/2; /* bisect the interval */
psumm=cheb_poly_eva(pt,xm,order);
if(psumm*psuml>0.)
{
psuml=psumm;
xl=xm;
}
else
{
psumr=psumm;
xr=xm;
}
}
/* once zero is found, reset initial interval to xr */
freq[j] = (xm);
xl = xm;
flag = 0; /* reset flag for next search */
}
else
{
psuml=temp_psumr;
xl=temp_xr;
}
}
}
/* convert from x domain to radians */
for(i=0; i<order; i++)
{
freq[i] = acosf(freq[i]);
}
return(roots);
}
/*---------------------------------------------------------------------------*\
FUNCTION....: cheb_poly_eva()
AUTHOR......: David Rowe
DATE CREATED: 24/2/93
This function evalutes a series of chebyshev polynomials
FIXME: performing memory allocation at run time is very inefficient,
replace with stack variables of MAX_P size.
\*---------------------------------------------------------------------------*/
float CQuantize::cheb_poly_eva(float *coef,float x,int order)
/* float coef[] coefficients of the polynomial to be evaluated */
/* float x the point where polynomial is to be evaluated */
/* int order order of the polynomial */
{
int i;
float *t,*u,*v,sum;
float T[(order / 2) + 1];
/* Initialise pointers */
t = T; /* T[i-2] */
*t++ = 1.0;
u = t--; /* T[i-1] */
*u++ = x;
v = u--; /* T[i] */
/* Evaluate chebyshev series formulation using iterative approach */
for(i=2; i<=order/2; i++)
*v++ = (2*x)*(*u++) - *t++; /* T[i] = 2*x*T[i-1] - T[i-2] */
sum=0.0; /* initialise sum to zero */
t = T; /* reset pointer */
/* Evaluate polynomial and return value also free memory space */
for(i=0; i<=order/2; i++)
sum+=coef[(order/2)-i]**t++;
return sum;
}

View File

@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------*\
FILE........: quantise.h
AUTHOR......: David Rowe
DATE CREATED: 31/5/92
Quantisation functions for the sinusoidal coder.
\*---------------------------------------------------------------------------*/
/*
All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License version 2.1, as
published by the Free Software Foundation. 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 Lesser General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __QUANTISE__
#define __QUANTISE__
#include <complex>
#include "qbase.h"
class CQuantize : public CQbase {
public:
void aks_to_M2(FFTR_STATE *fftr_fwd_cfg, float ak[], int order, MODEL *model, float E, float *snr, int sim_pf, int pf, int bass_boost, float beta, float gamma, std::complex<float> Aw[]);
int encode_Wo(C2CONST *c2const, float Wo, int bits);
float decode_Wo(C2CONST *c2const, int index, int bits);
void encode_lsps_scalar(int indexes[], float lsp[], int order);
void decode_lsps_scalar(float lsp[], int indexes[], int order);
void encode_lspds_scalar(int indexes[], float lsp[], int order);
void decode_lspds_scalar(float lsp[], int indexes[], int order);
int encode_energy(float e, int bits);
float decode_energy(int index, int bits);
void pack(unsigned char * bits, unsigned int *nbit, int index, unsigned int index_bits);
void pack_natural_or_gray(unsigned char * bits, unsigned int *nbit, int index, unsigned int index_bits, unsigned int gray);
int unpack(const unsigned char * bits, unsigned int *nbit, unsigned int index_bits);
int unpack_natural_or_gray(const unsigned char * bits, unsigned int *nbit, unsigned int index_bits, unsigned int gray);
int lsp_bits(int i);
int lspd_bits(int i);
void apply_lpc_correction(MODEL *model);
float speech_to_uq_lsps(float lsp[], float ak[], float Sn[], float w[], int m_pitch, int order);
int check_lsp_order(float lsp[], int lpc_order);
void bw_expand_lsps(float lsp[], int order, float min_sep_low, float min_sep_high);
private:
void compute_weights(const float *x, float *w, int ndim);
int find_nearest(const float *codebook, int nb_entries, float *x, int ndim);
void lpc_post_filter(FFTR_STATE *fftr_fwd_cfg, float Pw[], float ak[], int order, float beta, float gamma, int bass_boost, float E);
int lpc_to_lsp (float *a, int lpcrdr, float *freq, int nb, float delta);
float cheb_poly_eva(float *coef,float x,int order);
};
#endif