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:
parent
44649fe486
commit
4c85516741
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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_ */
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user