1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-25 17:28:50 -05:00

Added PCMA and PCMU encoding for UDP/RTP audio

This commit is contained in:
f4exb 2019-02-13 07:53:38 +01:00
parent bfb686742a
commit 1de6ea4e60
12 changed files with 323 additions and 49 deletions

View File

@ -17,6 +17,11 @@
#include "audiocompressor.h"
const uint16_t AudioCompressor::ALAW_MAX = 0xFFF;
const uint16_t AudioCompressor::MULAW_MAX = 0x1FFF;
const uint16_t AudioCompressor::MULAW_BIAS = 33;
AudioCompressor::AudioCompressor()
{
fillLUT2();
@ -79,9 +84,117 @@ void AudioCompressor::fillLUT2()
}
}
void AudioCompressor::fillALaw()
{
for (int i=0; i<8*4096; i++) {
m_lut[i] = ALaw_Encode(i/2 + 16384);
}
}
void AudioCompressor::fillULaw()
{
for (int i=0; i<8*4096; i++) {
m_lut[i] = MuLaw_Encode(i/2 + 16384);
}
}
int16_t AudioCompressor::compress(int16_t sample)
{
int16_t sign = sample < 0 ? -1 : 1;
int16_t abs = sample < 0 ? -sample : sample;
return sign * m_lut[abs];
}
int8_t AudioCompressor::compress8(int16_t sample)
{
return ALaw_Encode(sample);
//return m_lut[sample/2 + 16384];
}
/* http://dystopiancode.blogspot.com/2012/02/pcm-law-and-u-law-companding-algorithms.html
*
* First, the number is verified is its negative. If the number is negative he will be made
* positive and the sign variable (by default 0) will contain the value 0x80
* (so when it's OR-ed to the coded result it will determine it's sign).
*
* Since the A-Law algorithm considers numbers in the range 0x000 - 0xFFF
* (without considering the sign bit), if a number is bigger than 0xFFF, it will automatically
* made equal to 0xFFF in order to avoid further problems.
*
* The first step in determining the coded value is finding the position of the first bit
* who has a 1 value (excluding the sign bit). The search is started from position 11
* and is continued until a bit with the value 1 is find or until a position smaller than 5 is met.
*
* If the position is smaller than 5 (there was no 1 bit found on the positions 11-5),
* the least significant byte of the coded number is made equal the the bits 5,4,3,2
* of the original number. Otherwise the least significant bit of the coded number is equal
* to the first four bits who come after the first 1 bit encountered.
*
* In the end, the most significant byte of the coded number is computed according to the position
* of the first 1 bit (if not such this was found, then the position is considered).
*
* Also, before returning the result, the even bits of the result will be complemented
* (by XOR-ing with 0x55).
*/
int8_t AudioCompressor::ALaw_Encode(int16_t number)
{
uint16_t mask = 0x800;
uint8_t sign = 0;
uint8_t position = 11;
uint8_t lsb = 0;
if (number < 0)
{
number = -number;
sign = 0x80;
}
if (number > ALAW_MAX) {
number = ALAW_MAX;
}
for (; ((number & mask) != mask && position >= 5); mask >>= 1, position--) {
}
lsb = (number >> ((position == 4) ? (1) : (position - 4))) & 0x0f;
return (sign | ((position - 4) << 4) | lsb) ^ 0x55;
}
/* http://dystopiancode.blogspot.com/2012/02/pcm-law-and-u-law-companding-algorithms.html
*
* The µ-Law compression algorithm is very similar to the A-Law compression algorithm.
* The main difference is that the µ-Law uses 13 bits instead of 12 bits, so the position
* variable will be initialized with 12 instead of 11. In order to make sure that there will be
* no number without a 1 bit in the first 12-5 positions, a bias value is added to the number
* (in this case 33). So, since there is no special case (numbers who do not have a 1 bit in
* the first 12-5 positions), this makes the algorithm less complex by eliminating some condtions.
*
* Also in the end all bits are complemented, not just the even ones.
*/
int8_t AudioCompressor::MuLaw_Encode(int16_t number)
{
uint16_t mask = 0x1000;
uint8_t sign = 0;
uint8_t position = 12;
uint8_t lsb = 0;
if (number < 0)
{
number = -number;
sign = 0x80;
}
number += MULAW_BIAS;
if (number > MULAW_MAX) {
number = MULAW_MAX;
}
for (; ((number & mask) != mask && position >= 5); mask >>= 1, position--) {
}
lsb = (number >> (position - 4)) & 0x0f;
return (~(sign | ((position - 5) << 4) | lsb));
}

View File

@ -28,10 +28,20 @@ public:
~AudioCompressor();
void fillLUT(); //!< 4 bands
void fillLUT2(); //!< 8 bands (default)
void fillALaw(); //!< A-law compression to 8 bits
void fillULaw(); //!< u-law compression to 8 bits
int16_t compress(int16_t sample);
int8_t compress8(int16_t sample);
private:
int8_t ALaw_Encode(int16_t number);
int8_t MuLaw_Encode(int16_t number);
int16_t m_lut[32768];
static const uint16_t ALAW_MAX;
static const uint16_t MULAW_MAX;
static const uint16_t MULAW_BIAS;
};

View File

@ -42,15 +42,30 @@ QDataStream& operator>>(QDataStream& ds, AudioDeviceManager::InputDeviceInfo& in
QDataStream& operator<<(QDataStream& ds, const AudioDeviceManager::OutputDeviceInfo& info)
{
ds << info.sampleRate << info.udpAddress << info.udpPort << info.copyToUDP << info.udpUseRTP << (int) info.udpChannelMode;
ds << info.sampleRate
<< info.udpAddress
<< info.udpPort
<< info.copyToUDP
<< info.udpUseRTP
<< (int) info.udpChannelMode
<< (int) info.udpChannelCodec;
return ds;
}
QDataStream& operator>>(QDataStream& ds, AudioDeviceManager::OutputDeviceInfo& info)
{
int intChannelMode;
ds >> info.sampleRate >> info.udpAddress >> info.udpPort >> info.copyToUDP >> info.udpUseRTP >> intChannelMode;
int intChannelCodec;
ds >> info.sampleRate
>> info.udpAddress
>> info.udpPort
>> info.copyToUDP
>> info.udpUseRTP
>> intChannelMode
>> intChannelCodec;
info.udpChannelMode = (AudioOutput::UDPChannelMode) intChannelMode;
info.udpChannelCodec = (AudioOutput::UDPChannelCodec) intChannelCodec;
return ds;
}
@ -346,6 +361,7 @@ void AudioDeviceManager::startAudioOutput(int outputDeviceIndex)
bool copyAudioToUDP;
bool udpUseRTP;
AudioOutput::UDPChannelMode udpChannelMode;
AudioOutput::UDPChannelCodec udpChannelCodec;
QString deviceName;
if (getOutputDeviceName(outputDeviceIndex, deviceName))
@ -358,6 +374,7 @@ void AudioDeviceManager::startAudioOutput(int outputDeviceIndex)
copyAudioToUDP = false;
udpUseRTP = false;
udpChannelMode = AudioOutput::UDPChannelLeft;
udpChannelCodec = AudioOutput::UDPCodecL16;
}
else
{
@ -367,6 +384,7 @@ void AudioDeviceManager::startAudioOutput(int outputDeviceIndex)
copyAudioToUDP = m_audioOutputInfos[deviceName].copyToUDP;
udpUseRTP = m_audioOutputInfos[deviceName].udpUseRTP;
udpChannelMode = m_audioOutputInfos[deviceName].udpChannelMode;
udpChannelCodec = m_audioOutputInfos[deviceName].udpChannelCodec;
}
m_audioOutputs[outputDeviceIndex]->start(outputDeviceIndex, sampleRate);
@ -376,6 +394,7 @@ void AudioDeviceManager::startAudioOutput(int outputDeviceIndex)
m_audioOutputInfos[deviceName].copyToUDP = copyAudioToUDP;
m_audioOutputInfos[deviceName].udpUseRTP = udpUseRTP;
m_audioOutputInfos[deviceName].udpChannelMode = udpChannelMode;
m_audioOutputInfos[deviceName].udpChannelCodec = udpChannelCodec;
}
else
{
@ -588,7 +607,7 @@ void AudioDeviceManager::setOutputDeviceInfo(int outputDeviceIndex, const Output
audioOutput->setUdpDestination(deviceInfo.udpAddress, deviceInfo.udpPort);
audioOutput->setUdpUseRTP(deviceInfo.udpUseRTP);
audioOutput->setUdpChannelMode(deviceInfo.udpChannelMode);
audioOutput->setUdpChannelFormat(deviceInfo.udpChannelMode == AudioOutput::UDPChannelStereo, deviceInfo.sampleRate);
audioOutput->setUdpChannelFormat(deviceInfo.udpChannelCodec, deviceInfo.udpChannelMode == AudioOutput::UDPChannelStereo, deviceInfo.sampleRate);
qDebug("AudioDeviceManager::setOutputDeviceInfo: index: %d device: %s updated",
outputDeviceIndex, qPrintable(deviceName));
@ -756,6 +775,7 @@ void AudioDeviceManager::debugAudioOutputInfos() const
<< " udpPort: " << it.value().udpPort
<< " copyToUDP: " << it.value().copyToUDP
<< " udpUseRTP: " << it.value().udpUseRTP
<< " udpChannelMode: " << (int) it.value().udpChannelMode;
<< " udpChannelMode: " << (int) it.value().udpChannelMode
<< " udpChannelCodec: " << (int) it.value().udpChannelCodec;
}
}

View File

@ -75,6 +75,7 @@ public:
bool copyToUDP;
bool udpUseRTP;
AudioOutput::UDPChannelMode udpChannelMode;
AudioOutput::UDPChannelCodec udpChannelCodec;
friend QDataStream& operator<<(QDataStream& ds, const OutputDeviceInfo& info);
friend QDataStream& operator>>(QDataStream& ds, OutputDeviceInfo& info);
};

View File

@ -18,12 +18,14 @@
#include "audionetsink.h"
#include "util/rtpsink.h"
#include <QDebug>
#include <QUdpSocket>
const int AudioNetSink::m_udpBlockSize = 512;
AudioNetSink::AudioNetSink(QObject *parent) :
m_type(SinkUDP),
m_codec(CodecL16),
m_rtpBufferAudio(0),
m_bufferIndex(0),
m_port(9998)
@ -34,6 +36,7 @@ AudioNetSink::AudioNetSink(QObject *parent) :
AudioNetSink::AudioNetSink(QObject *parent, int sampleRate, bool stereo) :
m_type(SinkUDP),
m_codec(CodecL16),
m_rtpBufferAudio(0),
m_bufferIndex(0),
m_port(9998)
@ -95,10 +98,32 @@ void AudioNetSink::deleteDestination(const QString& address, uint16_t port)
}
}
void AudioNetSink::setParameters(bool stereo, int sampleRate)
void AudioNetSink::setParameters(Codec codec, bool stereo, int sampleRate)
{
if (m_rtpBufferAudio) {
m_rtpBufferAudio->setPayloadInformation(stereo ? RTPSink::PayloadL16Stereo : RTPSink::PayloadL16Mono, sampleRate);
qDebug() << "AudioNetSink::setParameters:"
<< " codec: " << codec
<< " stereo: " << stereo
<< " sampleRate: " << sampleRate;
m_codec = codec;
if (m_rtpBufferAudio)
{
switch (m_codec)
{
case CodecPCMA:
m_audioCompressor.fillALaw();
m_rtpBufferAudio->setPayloadInformation(RTPSink::PayloadPCMA8, sampleRate);
break;
case CodecPCMU:
m_audioCompressor.fillULaw();
m_rtpBufferAudio->setPayloadInformation(RTPSink::PayloadPCMU8, sampleRate);
break;
case CodecL16: // actually no codec
default:
m_rtpBufferAudio->setPayloadInformation(stereo ? RTPSink::PayloadL16Stereo : RTPSink::PayloadL16Mono, sampleRate);
break;
}
}
}
@ -113,14 +138,43 @@ void AudioNetSink::write(qint16 sample)
}
else
{
qint16 *p = (qint16*) &m_data[m_bufferIndex];
*p = sample;
m_bufferIndex += sizeof(qint16);
switch(m_codec)
{
case CodecPCMA:
case CodecPCMU:
{
qint8 *p = (qint8*) &m_data[m_bufferIndex];
*p = m_audioCompressor.compress8(sample);
m_bufferIndex += sizeof(qint8);
}
break;
case CodecL16:
default:
{
qint16 *p = (qint16*) &m_data[m_bufferIndex];
*p = sample;
m_bufferIndex += sizeof(qint16);
}
break;
}
}
}
else if (m_type == SinkRTP)
{
m_rtpBufferAudio->write((uint8_t *) &sample);
switch(m_codec)
{
case CodecPCMA:
case CodecPCMU:
{
qint8 p = m_audioCompressor.compress8(sample);
m_rtpBufferAudio->write((uint8_t *) &p);
}
break;
case CodecL16:
default:
m_rtpBufferAudio->write((uint8_t *) &sample);
break;
}
}
}
@ -149,35 +203,35 @@ void AudioNetSink::write(qint16 lSample, qint16 rSample)
}
}
void AudioNetSink::write(AudioSample* samples, uint32_t numSamples)
{
if (m_type == SinkUDP)
{
int samplesIndex = 0;
if (m_bufferIndex + numSamples*sizeof(AudioSample) >= m_udpBlockSize) // fill remainder of buffer and send it
{
memcpy(&m_data[m_bufferIndex], &samples[samplesIndex], m_udpBlockSize - m_bufferIndex); // fill remainder of buffer
m_udpSocket->writeDatagram((const char*)m_data, (qint64 ) m_udpBlockSize, m_address, m_port);
m_bufferIndex = 0;
samplesIndex += (m_udpBlockSize - m_bufferIndex) / sizeof(AudioSample);
numSamples -= (m_udpBlockSize - m_bufferIndex) / sizeof(AudioSample);
}
while (numSamples > m_udpBlockSize/sizeof(AudioSample)) // send directly from input without buffering
{
m_udpSocket->writeDatagram((const char*)&samples[samplesIndex], (qint64 ) m_udpBlockSize, m_address, m_port);
samplesIndex += m_udpBlockSize/sizeof(AudioSample);
numSamples -= m_udpBlockSize/sizeof(AudioSample);
}
memcpy(&m_data[m_bufferIndex], &samples[samplesIndex], numSamples*sizeof(AudioSample));
}
else if (m_type == SinkRTP)
{
m_rtpBufferAudio->write((uint8_t *) samples, numSamples*2); // 2 x 16 bit sample
}
}
//void AudioNetSink::write(AudioSample* samples, uint32_t numSamples)
//{
// if (m_type == SinkUDP)
// {
// int samplesIndex = 0;
//
// if (m_bufferIndex + numSamples*sizeof(AudioSample) >= m_udpBlockSize) // fill remainder of buffer and send it
// {
// memcpy(&m_data[m_bufferIndex], &samples[samplesIndex], m_udpBlockSize - m_bufferIndex); // fill remainder of buffer
// m_udpSocket->writeDatagram((const char*)m_data, (qint64 ) m_udpBlockSize, m_address, m_port);
// m_bufferIndex = 0;
// samplesIndex += (m_udpBlockSize - m_bufferIndex) / sizeof(AudioSample);
// numSamples -= (m_udpBlockSize - m_bufferIndex) / sizeof(AudioSample);
// }
//
// while (numSamples > m_udpBlockSize/sizeof(AudioSample)) // send directly from input without buffering
// {
// m_udpSocket->writeDatagram((const char*)&samples[samplesIndex], (qint64 ) m_udpBlockSize, m_address, m_port);
// samplesIndex += m_udpBlockSize/sizeof(AudioSample);
// numSamples -= m_udpBlockSize/sizeof(AudioSample);
// }
//
// memcpy(&m_data[m_bufferIndex], &samples[samplesIndex], numSamples*sizeof(AudioSample));
// }
// else if (m_type == SinkRTP)
// {
// m_rtpBufferAudio->write((uint8_t *) samples, numSamples*2); // 2 x 16 bit sample
// }
//}
void AudioNetSink::moveToThread(QThread *thread)
{

View File

@ -19,6 +19,7 @@
#define SDRBASE_AUDIO_AUDIONETSINK_H_
#include "dsp/dsptypes.h"
#include "audiocompressor.h"
#include "export.h"
#include <QObject>
@ -37,6 +38,13 @@ public:
SinkRTP
} SinkType;
typedef enum
{
CodecL16,
CodecPCMA,
CodecPCMU
} Codec;
AudioNetSink(QObject *parent); //!< without RTP
AudioNetSink(QObject *parent, int sampleRate, bool stereo); //!< with RTP
~AudioNetSink();
@ -44,11 +52,11 @@ public:
void setDestination(const QString& address, uint16_t port);
void addDestination(const QString& address, uint16_t port);
void deleteDestination(const QString& address, uint16_t port);
void setParameters(bool stereo, int sampleRate);
void setParameters(Codec codec, bool stereo, int sampleRate);
void write(qint16 sample);
void write(qint16 lSample, qint16 rSample);
void write(AudioSample* samples, uint32_t numSamples);
//void write(AudioSample* samples, uint32_t numSamples);
bool isRTPCapable() const;
bool selectType(SinkType type);
@ -59,8 +67,10 @@ public:
protected:
SinkType m_type;
Codec m_codec;
QUdpSocket *m_udpSocket;
RTPSink *m_rtpBufferAudio;
AudioCompressor m_audioCompressor;
char m_data[65536];
unsigned int m_bufferIndex;
QHostAddress m_address;

View File

@ -29,6 +29,7 @@ AudioOutput::AudioOutput() :
m_audioNetSink(0),
m_copyAudioToUdp(false),
m_udpChannelMode(UDPChannelLeft),
m_udpChannelCodec(UDPCodecL16),
m_audioUsageCount(0),
m_onExit(false),
m_audioFifos()
@ -200,10 +201,12 @@ void AudioOutput::setUdpChannelMode(UDPChannelMode udpChannelMode)
m_udpChannelMode = udpChannelMode;
}
void AudioOutput::setUdpChannelFormat(bool stereo, int sampleRate)
void AudioOutput::setUdpChannelFormat(UDPChannelCodec udpChannelCodec, bool stereo, int sampleRate)
{
m_udpChannelCodec = udpChannelCodec;
if (m_audioNetSink) {
m_audioNetSink->setParameters(stereo, sampleRate);
m_audioNetSink->setParameters((AudioNetSink::Codec) m_udpChannelCodec, stereo, sampleRate);
}
}

View File

@ -41,6 +41,13 @@ public:
UDPChannelStereo
};
enum UDPChannelCodec
{
UDPCodecL16, //!< Linear 16 bit (no codec)
UDPCodecALaw,
UDPCodecULaw
};
AudioOutput();
virtual ~AudioOutput();
@ -58,7 +65,7 @@ public:
void setUdpCopyToUDP(bool copyToUDP);
void setUdpUseRTP(bool useRTP);
void setUdpChannelMode(UDPChannelMode udpChannelMode);
void setUdpChannelFormat(bool stereo, int sampleRate);
void setUdpChannelFormat(UDPChannelCodec udpChannelCodec, bool stereo, int sampleRate);
private:
QMutex m_mutex;
@ -66,6 +73,7 @@ private:
AudioNetSink* m_audioNetSink;
bool m_copyAudioToUdp;
UDPChannelMode m_udpChannelMode;
UDPChannelCodec m_udpChannelCodec;
uint m_audioUsageCount;
bool m_onExit;

View File

@ -76,24 +76,37 @@ void RTPSink::setPayloadInformation(PayloadType payloadType, int sampleRate)
uint32_t timestampinc;
QMutexLocker locker(&m_mutex);
qDebug("RTPSink::setPayloadInformation: %d sampleRate: %d", payloadType, sampleRate);
qDebug("RTPSink::setPayloadInformation: payloadType: %d sampleRate: %d", payloadType, sampleRate);
switch (payloadType)
{
case PayloadPCMA8:
m_sampleBytes = 1;
m_rtpSession.SetDefaultPayloadType(8);
m_packetSamples = m_sampleRate / 50; // 20ms packet samples
timestampinc = m_sampleRate / 50; // 8k -> 160 packets in 20ms
break;
case PayloadPCMU8:
m_sampleBytes = 1;
m_rtpSession.SetDefaultPayloadType(0);
m_packetSamples = m_sampleRate / 50; // 20ms packet samples
timestampinc = m_sampleRate / 50; // 8k -> 160 packets in 20ms
break;
case PayloadL16Stereo:
m_sampleBytes = 4;
m_rtpSession.SetDefaultPayloadType(96);
m_packetSamples = m_sampleRate / 50; // 20ms packet samples
timestampinc = m_sampleRate / 100;
break;
case PayloadL16Mono:
default:
m_sampleBytes = 2;
m_rtpSession.SetDefaultPayloadType(96);
m_packetSamples = m_sampleRate / 50; // 20ms packet samples
timestampinc = m_sampleRate / 50;
break;
}
m_packetSamples = m_sampleRate/50; // 20ms packet samples
m_bufferSize = m_packetSamples * m_sampleBytes;
if (m_byteBuffer) {
@ -299,6 +312,10 @@ unsigned int RTPSink::elemLength(PayloadType payloadType)
{
switch (payloadType)
{
case PayloadPCMA8:
case PayloadPCMU8:
return sizeof(int8_t);
break;
case PayloadL16Stereo:
return sizeof(int16_t);
break;

View File

@ -42,6 +42,8 @@ public:
{
PayloadL16Mono,
PayloadL16Stereo,
PayloadPCMA8,
PayloadPCMU8
} PayloadType;
RTPSink(QUdpSocket *udpSocket, int sampleRate, bool stereo);

View File

@ -219,6 +219,7 @@ void AudioDialogX::updateOutputDisplay()
ui->outputUDPCopy->setChecked(m_outputDeviceInfo.copyToUDP);
ui->outputUDPUseRTP->setChecked(m_outputDeviceInfo.udpUseRTP);
ui->outputUDPChannelMode->setCurrentIndex((int) m_outputDeviceInfo.udpChannelMode);
ui->outputUDPChannelCodec->setCurrentIndex((int) m_outputDeviceInfo.udpChannelCodec);
}
void AudioDialogX::updateOutputDeviceInfo()
@ -229,5 +230,6 @@ void AudioDialogX::updateOutputDeviceInfo()
m_outputDeviceInfo.copyToUDP = ui->outputUDPCopy->isChecked();
m_outputDeviceInfo.udpUseRTP = ui->outputUDPUseRTP->isChecked();
m_outputDeviceInfo.udpChannelMode = (AudioOutput::UDPChannelMode) ui->outputUDPChannelMode->currentIndex();
m_outputDeviceInfo.udpChannelCodec = (AudioOutput::UDPChannelCodec) ui->outputUDPChannelCodec->currentIndex();
}

View File

@ -6,10 +6,16 @@
<rect>
<x>0</x>
<y>0</y>
<width>472</width>
<height>349</height>
<width>560</width>
<height>400</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>560</width>
<height>400</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
@ -231,6 +237,34 @@
</item>
</widget>
</item>
<item>
<widget class="QComboBox" name="outputUDPChannelCodec">
<property name="minimumSize">
<size>
<width>85</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Encoding</string>
</property>
<item>
<property name="text">
<string>L16</string>
</property>
</item>
<item>
<property name="text">
<string>PCMA/8k</string>
</property>
</item>
<item>
<property name="text">
<string>PCMU/8k</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QCheckBox" name="outputUDPUseRTP">
<property name="toolTip">