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

Copy audio to UDP/RTP: Opus implementation (2)

This commit is contained in:
f4exb 2019-02-18 18:29:37 +01:00
parent 44649fe486
commit 4c85516741
6 changed files with 164 additions and 19 deletions

View File

@ -30,8 +30,11 @@ AudioNetSink::AudioNetSink(QObject *parent) :
m_codec(CodecL16),
m_rtpBufferAudio(0),
m_sampleRate(48000),
m_stereo(false),
m_decimation(1),
m_decimationCount(0),
m_codecInputSize(960),
m_codecInputIndex(0),
m_bufferIndex(0),
m_port(9998)
{
@ -44,8 +47,11 @@ AudioNetSink::AudioNetSink(QObject *parent, int sampleRate, bool stereo) :
m_codec(CodecL16),
m_rtpBufferAudio(0),
m_sampleRate(48000),
m_stereo(false),
m_decimation(1),
m_decimationCount(0),
m_codecInputSize(960),
m_codecInputIndex(0),
m_bufferIndex(0),
m_port(9998)
{
@ -114,9 +120,10 @@ void AudioNetSink::setParameters(Codec codec, bool stereo, int sampleRate)
<< " sampleRate: " << sampleRate;
m_codec = codec;
m_stereo = stereo;
m_sampleRate = sampleRate;
setDecimationFilters();
setNewCodecData();
if (m_rtpBufferAudio)
{
@ -151,10 +158,28 @@ void AudioNetSink::setDecimation(uint32_t decimation)
{
m_decimation = decimation < 1 ? 1 : decimation > 6 ? 6 : decimation;
qDebug() << "AudioNetSink::setDecimation: " << m_decimation << " from: " << decimation;
setDecimationFilters();
setNewCodecData();
m_decimationCount = 0;
}
void AudioNetSink::setNewCodecData()
{
if (m_codec == CodecOpus)
{
m_codecInputSize = m_sampleRate / (m_decimation * 50); // 20ms = 1/50s - size is per channel
m_codecInputSize = m_codecInputSize > 960 ? 960 : m_codecInputSize; // hard limit of 48 kS/s
m_codecRatio = (m_sampleRate / m_decimation) / (AudioOpus::m_bitrate / 8); // compressor ratio
qDebug() << "AudioNetSink::setNewCodecData: CodecOpus:"
<< " m_codecInputSize: " << m_codecInputSize
<< " m_codecRatio: " << m_codecRatio
<< " Fs: " << m_sampleRate/m_decimation
<< " stereo: " << m_stereo;
m_opus.setEncoder(m_sampleRate/m_decimation, m_stereo ? 2 : 1);
}
setDecimationFilters();
}
void AudioNetSink::setDecimationFilters()
{
int decimatedSampleRate = m_sampleRate / m_decimation;
@ -206,10 +231,6 @@ void AudioNetSink::write(qint16 isample)
m_udpSocket->writeDatagram((const char*) m_data, (qint64 ) m_udpBlockSize, m_address, m_port);
m_bufferIndex = 0;
}
}
else if (m_codec == CodecOpus)
{
}
else
{
@ -250,7 +271,15 @@ void AudioNetSink::write(qint16 isample)
break;
case CodecOpus:
{
if (m_codecInputIndex == m_codecInputSize)
{
int nbBytes = m_opus.encode(m_codecInputSize, m_opusIn, (uint8_t *) m_data);
nbBytes = nbBytes > m_udpBlockSize ? m_udpBlockSize : nbBytes;
m_udpSocket->writeDatagram((const char*) m_data, (qint64 ) nbBytes, m_address, m_port);
m_codecInputIndex = 0;
}
m_opusIn[m_codecInputIndex++] = sample;
}
break;
case CodecL16:
@ -289,7 +318,7 @@ void AudioNetSink::write(qint16 isample)
m_bufferIndex = 0;
}
if (m_bufferIndex%2 == 0) {
if (m_bufferIndex % 2 == 0) {
m_rtpBufferAudio->write((uint8_t *) &m_data[m_bufferIndex/2]);
}
@ -298,6 +327,25 @@ void AudioNetSink::write(qint16 isample)
m_bufferIndex += 1;
}
break;
case CodecOpus:
{
if (m_codecInputIndex == m_codecInputSize)
{
int nbBytes = m_opus.encode(m_codecInputSize, m_opusIn, (uint8_t *) m_data);
if (nbBytes != AudioOpus::m_bitrate/400) { // 8 bits for 1/50s (20ms)
qWarning("AudioNetSink::write: CodecOpus mono: unexpected output frame size: %d bytes", nbBytes);
}
m_bufferIndex = 0;
m_codecInputIndex = 0;
}
if (m_codecInputIndex % m_codecRatio == 0) {
m_rtpBufferAudio->write((uint8_t *) &m_data[m_bufferIndex++]);
}
m_opusIn[m_codecInputIndex++] = sample;
}
break;
case CodecL16:
default:
m_rtpBufferAudio->write((uint8_t *) &sample);
@ -333,10 +381,43 @@ void AudioNetSink::write(qint16 ilSample, qint16 irSample)
{
if (m_bufferIndex >= m_udpBlockSize)
{
m_udpSocket->writeDatagram((const char*)m_data, (qint64 ) m_udpBlockSize, m_address, m_port);
m_udpSocket->writeDatagram((const char*) m_data, (qint64 ) m_udpBlockSize, m_address, m_port);
m_bufferIndex = 0;
}
else
switch(m_codec)
{
case CodecPCMA:
case CodecPCMU:
case CodecG722:
break; // mono modes - do nothing
case CodecOpus:
{
if (m_codecInputIndex == m_codecInputSize)
{
int nbBytes = m_opus.encode(m_codecInputSize, m_opusIn, (uint8_t *) m_data);
nbBytes = nbBytes > m_udpBlockSize ? m_udpBlockSize : nbBytes;
m_udpSocket->writeDatagram((const char*) m_data, (qint64 ) nbBytes, m_address, m_port);
m_codecInputIndex = 0;
}
m_opusIn[2*m_codecInputIndex] = lSample;
m_opusIn[2*m_codecInputIndex+1] = rSample;
m_codecInputIndex++;
}
break;
case CodecL8:
{
qint8 *p = (qint8*) &m_data[m_bufferIndex];
*p = lSample / 256;
m_bufferIndex += sizeof(qint8);
p = (qint8*) &m_data[m_bufferIndex];
*p = rSample / 256;
m_bufferIndex += sizeof(qint8);
}
break;
case CodecL16:
default:
{
qint16 *p = (qint16*) &m_data[m_bufferIndex];
*p = lSample;
@ -345,10 +426,50 @@ void AudioNetSink::write(qint16 ilSample, qint16 irSample)
*p = rSample;
m_bufferIndex += sizeof(qint16);
}
break;
}
}
else if (m_type == SinkRTP)
{
m_rtpBufferAudio->write((uint8_t *) &lSample, (uint8_t *) &rSample);
switch(m_codec)
{
case CodecPCMA:
case CodecPCMU:
case CodecG722:
break; // mono modes - do nothing
case CodecOpus:
{
if (m_codecInputIndex == m_codecInputSize)
{
int nbBytes = m_opus.encode(m_codecInputSize, m_opusIn, (uint8_t *) m_data);
if (nbBytes != AudioOpus::m_bitrate/400) { // 8 bits for 1/50s (20ms)
qWarning("AudioNetSink::write: CodecOpus stereo: unexpected output frame size: %d bytes", nbBytes);
}
m_bufferIndex = 0;
m_codecInputIndex = 0;
}
if (m_codecInputIndex % m_codecRatio == 0) {
m_rtpBufferAudio->write((uint8_t *) &m_data[m_bufferIndex++]);
}
m_opusIn[2*m_codecInputIndex] = lSample;
m_opusIn[2*m_codecInputIndex+1] = rSample;
m_codecInputIndex++;
}
break;
case CodecL8:
{
qint8 pl = lSample / 256;
qint8 pr = rSample / 256;
m_rtpBufferAudio->write((uint8_t *) &pl, (uint8_t *) &pr);
}
break;
case CodecL16:
default:
m_rtpBufferAudio->write((uint8_t *) &lSample, (uint8_t *) &rSample);
break;
}
}
}

View File

@ -22,6 +22,7 @@
#include "audiofilter.h"
#include "audiocompressor.h"
#include "audiog722.h"
#include "audioopus.h"
#include "export.h"
#include <QObject>
@ -72,9 +73,11 @@ public:
static const int m_dataBlockSize = 65536; // room for G722 conversion (64000 = 12800*5 largest to date)
static const int m_g722BlockSize = 12800; // number of resulting G722 bytes (80*20ms frames)
static const int m_opusBlockSize = 960*2; // provision for 20ms of 2 int16 channels at 48 kS/s
static const int m_opusOutputSize = 160; // output frame: 20ms of 8 bit data @ 64 kbits/s = 160 bytes
protected:
void setDecimationFilters();
void setNewCodecData(); // actions to take when changes affecting codec dependent data occurs
void setDecimationFilters(); // set decimation filters limits depending on effective sample rate and codec
SinkType m_type;
Codec m_codec;
@ -82,18 +85,20 @@ protected:
RTPSink *m_rtpBufferAudio;
AudioCompressor m_audioCompressor;
AudioG722 m_g722;
AudioOpus m_opus;
AudioFilter m_audioFilter;
int m_sampleRate;
bool m_stereo;
uint32_t m_decimation;
uint32_t m_decimationCount;
char m_data[m_dataBlockSize];
uint16_t m_opusIn[m_opusBlockSize];
int16_t m_opusIn[m_opusBlockSize];
int m_codecInputSize; // codec input block size - for codecs with actual encoding (Opus only for now)
int m_codecInputIndex; // codec input block fill index
int m_codecRatio; // codec compression ratio
unsigned int m_bufferIndex;
QHostAddress m_address;
unsigned int m_port;
};
#endif /* SDRBASE_AUDIO_AUDIONETSINK_H_ */

View File

@ -2,6 +2,9 @@
// Copyright (C) 2019 F4EXB //
// written by Edouard Griffiths //
// //
// In this version we will use a fixed constant bit rate of 64kbit/s. //
// With a frame time of 20ms the encoder output size is always 160 bytes. //
// //
// 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 as version 3 of the License, or //
@ -69,6 +72,15 @@ void AudioOpus::setEncoder(int32_t fs, int nChannels)
m_encoderOK = false;
return;
}
error = opus_encoder_ctl(m_encoderState, OPUS_SET_VBR(0)); // force constant bit rate
if (error != OPUS_OK)
{
qWarning("AudioOpus::setEncoder: set constant bitrate error: %s", opus_strerror(error));
m_encoderOK = false;
return;
}
}
int AudioOpus::encode(int frameSize, int16_t *in, uint8_t *out)
@ -77,7 +89,7 @@ int AudioOpus::encode(int frameSize, int16_t *in, uint8_t *out)
if (nbBytes < 0)
{
qWarning("AudioOpus::encode failed: %s\n", opus_strerror(nbBytes));
qWarning("AudioOpus::encode failed: %s", opus_strerror(nbBytes));
return 0;
}
else

View File

@ -113,8 +113,8 @@ void RTPSink::setPayloadInformation(PayloadType payloadType, int sampleRate)
case PayloadOpus:
m_sampleBytes = 1;
m_rtpSession.SetDefaultPayloadType(101);
m_packetSamples = 160; // Fixed 20ms @ 64 kbits/s packet samples
timestampinc = 160; // 1 channel
m_packetSamples = 960; // Fixed 20ms @ 48 kHz as per https://tools.ietf.org/html/rfc7587
timestampinc = 960; // and per single channel
break;
case PayloadL16Mono:
default:

View File

@ -67,7 +67,7 @@ This is the codec applied before sending the stream via UDP. The following are a
- `PCMA`: A-law 8 bit PCM (requires 8000 Hz sample rate mono)
- `PCMU`: Mu-law 8 bit PCM (requires 8000 Hz sample rate mono)
- `G722`: G722 64 kbit/s (requires 16000 Hz sample rate mono)
- `OPUS` : Opus 64 kbit/s
- `OPUS` : Opus constant 64 kbit/s (requires 48, 24, 16 or 12 kHz sample rates)
<h3>1.10 SDP string</h3>

View File

@ -350,4 +350,11 @@ void AudioDialogX::check()
QMessageBox::information(this, tr("Message"), tr("G722 must be 16000 Hz single channel"));
}
}
else if (m_outputDeviceInfo.udpChannelCodec == AudioOutput::UDPCodecOpus)
{
int effectiveSampleRate = m_outputDeviceInfo.sampleRate/decimationFactor;
if ((effectiveSampleRate != 48000) || (effectiveSampleRate != 24000) || (effectiveSampleRate != 16000) || (effectiveSampleRate != 12000)) {
QMessageBox::information(this, tr("Message"), tr("Opus takes only 48, 24, 16 or 12 kHz sample rates"));
}
}
}