Skip to content

Commit 7e0c416

Browse files
author
armaan
committed
Full Implementation: Support for custom recording samplerates across output file formats and encoders.
All bugs fixed.
1 parent 6516549 commit 7e0c416

17 files changed

Lines changed: 351 additions & 165 deletions

.gitignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,3 @@ src/mixxx.rc.include
6666
src/mixxx.res
6767
src/test/**/*.actual
6868
res/qrc_mixxx.cc
69-
70-
res/translations/source_copy_allow_list*.tsv

src/coreservices.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,6 @@ void CoreServices::initializeSettings() {
436436
}
437437
#endif
438438
QString settingsPath = m_cmdlineArgs.getSettingsPath();
439-
qDebug() << settingsPath;
440439
m_pSettingsManager = std::make_unique<SettingsManager>(settingsPath);
441440
}
442441

@@ -650,7 +649,6 @@ void CoreServices::initialize(QApplication* pApp) {
650649
// Initialize controller sub-system,
651650
// but do not set up controllers until the end of the application startup
652651
// (long)
653-
qDebug() << "Creating ControllerManager";
654652
m_pControllerManager = std::make_shared<ControllerManager>(pConfig);
655653

656654
// Scan the library for new files and directories

src/encoder/encoder.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ EncoderPointer EncoderFactory::createEncoder(
126126
DEBUG_ASSERT(false);
127127
pEncoder = std::make_shared<EncoderWave>(pCallback);
128128
}
129+
pEncoder->initResampler();
129130
return pEncoder;
130131
}
131132

@@ -155,3 +156,20 @@ EncoderRecordingSettingsPointer EncoderFactory::getEncoderRecordingSettings(Enco
155156
return std::make_shared<EncoderWaveSettings>(pConfig, ENCODING_WAVE);
156157
}
157158
}
159+
160+
void Encoder::initResampler() {
161+
m_pRecResampler = std::make_unique<EngineBufferScaleSR>();
162+
}
163+
164+
double Encoder::recResampleBuffer(
165+
const CSAMPLE* pInputBuffer,
166+
CSAMPLE* pOutputBuffer,
167+
SINT iInputBufferSize, /*#samples*/
168+
double baseRate) {
169+
double outputFramesGenerated{};
170+
outputFramesGenerated = m_pRecResampler->recScaleBuffer(pInputBuffer,
171+
pOutputBuffer,
172+
iInputBufferSize,
173+
baseRate); // needs to consume all input frames.
174+
return outputFramesGenerated;
175+
}

src/encoder/encoder.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include "encoder/encoderrecordingsettings.h"
66
#include "encoder/encodersettings.h"
7+
#include "engine/bufferscalers/enginebufferscalesr.h"
78
#include "preferences/usersettings.h"
89
#include "util/types.h"
910

@@ -24,7 +25,8 @@ class Encoder {
2425
QString fileExtension;
2526
};
2627

27-
Encoder() {}
28+
Encoder()
29+
: m_pRecResampler(nullptr) {};
2830
virtual ~Encoder() = default;
2931

3032
virtual int initEncoder(mixxx::audio::SampleRate sampleRate, QString* pUserErrorMessage) = 0;
@@ -37,6 +39,18 @@ class Encoder {
3739
virtual void flush() = 0;
3840
// Setup the encoder with the specific settings
3941
virtual void setEncoderSettings(const EncoderSettings& settings) = 0;
42+
// Create a bufferscaler object to resample the input buffer (at engine samplerate)
43+
// to recording samplerate
44+
virtual void initResampler();
45+
46+
double recResampleBuffer(
47+
const CSAMPLE* pInputBuffer,
48+
CSAMPLE* pOutputBuffer,
49+
SINT iInputBufferSize, /*#samples*/
50+
double baseRate);
51+
52+
private:
53+
std::unique_ptr<EngineBufferScaleSR> m_pRecResampler;
4054
};
4155

4256
typedef std::shared_ptr<Encoder> EncoderPointer;

src/encoder/encoderfdkaac.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
namespace {
1818
// recommended in encoder documentation, section 2.4.1
1919
constexpr int kOutBufferBits = 6144;
20+
21+
// 96Khz/8Khz (max_allowed_sample_rate / min_allowed_sample_rate)
22+
constexpr int kMaxInterpolationFactor = 12;
2023
const mixxx::Logger kLogger("EncoderFdkAac");
2124
} // namespace
2225

@@ -345,7 +348,8 @@ int EncoderFdkAac::initEncoder(mixxx::audio::SampleRate sampleRate, QString* pUs
345348
delete m_pInputFifo;
346349
m_pInputFifo = nullptr;
347350
}
348-
m_pInputFifo = new FIFO<SAMPLE>(EngineSideChain::SIDECHAIN_BUFFER_SIZE * 2);
351+
m_pInputFifo = new FIFO<SAMPLE>(EngineSideChain::SIDECHAIN_BUFFER_SIZE * 2 *
352+
kMaxInterpolationFactor);
349353

350354
m_pFifoChunkBuffer.resize(m_readRequired * sizeof(SAMPLE));
351355
return 0;

src/encoder/encodermp3.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ void EncoderMp3::encodeBuffer(const CSAMPLE* samples, const std::size_t bufferSi
147147
int rc = 0;
148148

149149
outsize = (int)((1.25 * bufferSize + 7200) + 1);
150-
bufferOutGrow(outsize);
150+
bufferOutGrow(outsize); // assuming this is to store the LAME-interpolated samples.
151151

152152
bufferInGrow(bufferSize);
153153

@@ -179,7 +179,8 @@ void EncoderMp3::initStream() {
179179
}
180180

181181
int EncoderMp3::initEncoder(mixxx::audio::SampleRate sampleRate, QString* pUserErrorMessage) {
182-
unsigned long samplerate_in = sampleRate;
182+
unsigned long samplerate_in = sampleRate; // rec samplerate, i.e. samplerate
183+
// of input stream to encoder
183184
// samplerate_out 0 means "let LAME pick the appropriate one"
184185
unsigned long samplerate_out = (samplerate_in > 48000 ? 48000 : 0);
185186

src/encoder/encoderopus.cpp

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ constexpr int kMaxOpusBufferSize = 1+1275;
1919
// Opus frame duration in milliseconds. Fixed to 60ms
2020
constexpr int kOpusFrameMs = 60;
2121
constexpr int kOpusChannelCount = 2;
22+
constexpr int kMaxInterpolationFactor = 12; // when we have a 96khz/8khz recording base rate.
23+
2224
// Opus only supports 48 and 96 kHz samplerates
25+
// XXX Redundant after both record,broadcast resampling are implemented.
2326
constexpr mixxx::audio::SampleRate kMainSampleRate = mixxx::audio::SampleRate(48000);
2427

2528
const mixxx::Logger kLogger("EncoderOpus");
@@ -71,34 +74,35 @@ int getSerial() {
7174
}
7275
} // namespace
7376

74-
//static
77+
// static
7578
mixxx::audio::SampleRate EncoderOpus::getMainSampleRate() {
7679
return kMainSampleRate;
7780
}
7881

79-
//static
82+
// static
8083
QString EncoderOpus::getInvalidSamplerateMessage() {
8184
return QObject::tr(
8285
"Using Opus at samplerates other than 48 kHz "
8386
"is not supported by the Opus encoder. Please use "
8487
"48000 Hz in \"Sound Hardware\" preferences "
85-
"or switch to a different encoding.");
88+
", switch to a different encoding "
89+
", or choose a custom recording samplerate.");
8690
};
8791

8892
EncoderOpus::EncoderOpus(EncoderCallback* pCallback)
89-
: m_bitrate(0),
90-
m_bitrateMode(0),
91-
m_channels(0),
92-
m_readRequired(0),
93-
m_pCallback(pCallback),
94-
m_fifoBuffer(EngineSideChain::SIDECHAIN_BUFFER_SIZE * kOpusChannelCount),
95-
m_pFifoChunkBuffer(),
96-
m_pOpus(nullptr),
97-
m_opusDataBuffer(kMaxOpusBufferSize),
98-
m_header_write(false),
99-
m_packetNumber(0),
100-
m_granulePos(0)
101-
{
93+
: m_bitrate(0),
94+
m_bitrateMode(0),
95+
m_channels(0),
96+
m_readRequired(0),
97+
m_pCallback(pCallback),
98+
m_fifoBuffer(EngineSideChain::SIDECHAIN_BUFFER_SIZE *
99+
kOpusChannelCount * kMaxInterpolationFactor),
100+
m_pFifoChunkBuffer(),
101+
m_pOpus(nullptr),
102+
m_opusDataBuffer(kMaxOpusBufferSize),
103+
m_header_write(false),
104+
m_packetNumber(0),
105+
m_granulePos(0) {
102106
// Regarding m_pFifoBuffer:
103107
// Size the input FIFO buffer with twice the maximum possible sample count that can be
104108
// processed at once, to avoid skipping frames or waiting for the required sample count
@@ -139,19 +143,6 @@ void EncoderOpus::setEncoderSettings(const EncoderSettings& settings) {
139143
int EncoderOpus::initEncoder(mixxx::audio::SampleRate sampleRate, QString* pUserErrorMessage) {
140144
Q_UNUSED(pUserErrorMessage);
141145

142-
if (sampleRate != kMainSampleRate) {
143-
kLogger.warning() << "initEncoder failed: samplerate not supported by Opus";
144-
145-
const QString invalidSamplerateMessage = getInvalidSamplerateMessage();
146-
147-
ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties();
148-
props->setType(DLG_WARNING);
149-
props->setTitle(QObject::tr("Encoder"));
150-
props->setText(invalidSamplerateMessage);
151-
props->setKey(invalidSamplerateMessage);
152-
ErrorDialogHandler::instance()->requestErrorDialog(props);
153-
return -1;
154-
}
155146
m_sampleRate = sampleRate;
156147
DEBUG_ASSERT(m_sampleRate == 8000 || m_sampleRate == 12000 ||
157148
m_sampleRate == 16000 || m_sampleRate == 24000 ||

src/encoder/encoderwave.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@ static sf_count_t sf_f_read (void *ptr, sf_count_t count, void *user_data)
5050
// with ptr and return the count of actually processed bytes.
5151
static sf_count_t sf_f_write (const void *ptr, sf_count_t count, void *user_data)
5252
{
53+
qDebug() << "in sf_f_write";
5354
EncoderCallback* pCallback = static_cast<EncoderCallback*>(user_data);
54-
pCallback->write(nullptr, static_cast<const unsigned char*>(ptr), 0, static_cast<int>(count));
55+
pCallback->write(nullptr,
56+
static_cast<const unsigned char*>(ptr),
57+
0,
58+
static_cast<int>(count)); // EngineRecord::write()
5559
return count;
5660
}
5761

@@ -62,9 +66,6 @@ static sf_count_t sf_f_tell (void *user_data)
6266
return pCallback->tell();
6367
}
6468

65-
66-
67-
6869
EncoderWave::EncoderWave(EncoderCallback* pCallback)
6970
: m_pCallback(pCallback),
7071
m_pSndfile(nullptr) {
@@ -138,7 +139,7 @@ void EncoderWave::flush() {
138139

139140
void EncoderWave::encodeBuffer(const CSAMPLE* pBuffer, const std::size_t bufferSize) {
140141
qDebug() << "wave encoding buffer";
141-
sf_write_float(m_pSndfile, pBuffer, bufferSize);
142+
sf_write_float(m_pSndfile, pBuffer, bufferSize); // sndfile core calls user-defined sf_f_write
142143
}
143144

144145
/* Originally called from enginebroadcast.cpp to update metadata information

src/engine/bufferscalers/enginebufferscalesr.cpp

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,81 @@
22

33
#include <QDebug>
44

5+
#include "engine/enginebuffer.h"
56
#include "engine/readaheadmanager.h"
67
#include "moc_enginebufferscalesr.cpp"
78
#include "util/math.h"
89
#include "util/sample.h"
910

10-
EngineBufferScaleSR::EngineBufferScaleSR(ReadAheadManager* pReadAheadManager)
11-
: m_pReadAheadManager(pReadAheadManager),
12-
m_bBackwards(false) {
13-
qDebug() << "constructed enginebufferscaleSR";
11+
namespace {
12+
constexpr int kMaxInterpolationFactor = 12;
13+
} // namespace
14+
15+
EngineBufferScaleSR::EngineBufferScaleSR()
16+
: m_pResampler(nullptr) {
17+
int error{};
18+
m_dChannels = getOutputSignal().getChannelCount();
19+
m_pResampler = src_new(SRC_SINC_BEST_QUALITY, m_dChannels.value(), &error);
20+
if (error || !m_pResampler) {
21+
qWarning() << "libsamplerate initialization error:" << src_strerror(error);
22+
m_pResampler = nullptr;
23+
}
24+
25+
clear();
1426
}
1527

1628
EngineBufferScaleSR::~EngineBufferScaleSR() {
1729
}
1830

19-
void EngineBufferScaleSR::setQuality(double engine_quality) {
20-
m_pEngineQuality = engine_quality;
31+
void EngineBufferScaleSR::clear() {
32+
if (m_pResampler) {
33+
src_reset(m_pResampler);
34+
}
2135
}
2236

23-
// these parameters describe the "request" that
24-
// needs to be handled by libsamplerate.
25-
// use the default implementation for now.
26-
void EngineBufferScaleSR::setScaleParameters(double base_rate,
27-
double* pTempoRatio,
28-
double* pPitchRatio) {
29-
(void)base_rate;
30-
(void)pTempoRatio;
31-
(void)pPitchRatio;
32-
}
37+
// baseRate: engine rate/recordingrate
38+
double EngineBufferScaleSR::recScaleBuffer(const CSAMPLE* pInputBuffer,
39+
CSAMPLE* pOutputBuffer,
40+
SINT iInputBufferSize,
41+
double srcRatio) {
42+
if (!pOutputBuffer || !pInputBuffer || iInputBufferSize <= 0) {
43+
return 0.0;
44+
}
45+
qDebug() << "input buffer size samples (from sidechain): " << iInputBufferSize;
3346

34-
void EngineBufferScaleSR::onSignalChanged() {
35-
qDebug() << "onSignalChanged called";
36-
}
47+
// create SRC_DATA struct
48+
SRC_DATA src_data;
49+
src_data.data_in = pInputBuffer;
3750

38-
void EngineBufferScaleSR::clear() {
39-
qDebug() << "clear called";
51+
// frames that need to be resampled
52+
SINT numInputFrames = getOutputSignal().samples2frames(iInputBufferSize);
53+
src_data.input_frames = numInputFrames;
54+
src_data.data_out = pOutputBuffer;
55+
56+
// max # output frames = (96khz/8khz) * #input frames
57+
src_data.output_frames = kMaxInterpolationFactor * numInputFrames;
58+
59+
// conversion ratio
60+
src_data.src_ratio = srcRatio;
61+
src_data.end_of_input = 0;
62+
63+
int error = src_process(m_pResampler, &src_data);
64+
if (error) {
65+
qWarning() << "libsamplerate error:" << src_strerror(error);
66+
}
67+
68+
SINT frames_generated = src_data.output_frames_gen;
69+
qDebug() << "SRC consumed " << src_data.input_frames_used << " input frames, produced "
70+
<< frames_generated << " output frames";
71+
72+
return frames_generated;
4073
}
4174

42-
// not called during regular playback
43-
double EngineBufferScaleSR::scaleBuffer(
44-
CSAMPLE* pOutputBuffer,
45-
SINT iOutputBufferSize) {
46-
qDebug() << "scaling using libsamplerate";
47-
(void)pOutputBuffer;
48-
(void)iOutputBufferSize;
75+
double EngineBufferScaleSR::scaleBuffer(CSAMPLE* /*pBuffer*/, SINT /*iBufferSize*/) {
76+
qWarning() << "Using temporary stub implementation";
4977
return 1.0;
5078
}
79+
80+
void EngineBufferScaleSR::onSignalChanged() {
81+
qWarning() << "Using temporary stub implementation";
82+
}

src/engine/bufferscalers/enginebufferscalesr.h

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,24 @@ class ReadAheadManager;
1818
class EngineBufferScaleSR : public EngineBufferScale {
1919
Q_OBJECT
2020
public:
21-
explicit EngineBufferScaleSR(
22-
ReadAheadManager* pReadAheadManager);
21+
explicit EngineBufferScaleSR(); // input driven
2322
~EngineBufferScaleSR() override;
2423

25-
void setScaleParameters(double base_rate,
26-
double* pTempoRatio,
27-
double* pPitchRatio) override;
28-
29-
void setQuality(double engine_quality);
30-
3124
// Main scaler method
3225
double scaleBuffer(
3326
CSAMPLE* pOutputBuffer,
3427
SINT iOutputBufferSize) override;
3528

29+
double recScaleBuffer(const CSAMPLE* pInputBuffer,
30+
CSAMPLE* pOutputBuffer,
31+
SINT iInputBufferSize,
32+
double baseRate);
33+
3634
void clear() override;
3735

3836
private:
3937
void onSignalChanged() override;
4038

41-
ReadAheadManager* m_pReadAheadManager;
42-
43-
bool m_bBackwards;
44-
double m_pEngineQuality;
39+
mixxx::audio::ChannelCount m_dChannels;
40+
SRC_STATE* m_pResampler;
4541
};

0 commit comments

Comments
 (0)