394 lines
14 KiB
C++
394 lines
14 KiB
C++
/*
|
|
Copyright (C) 2008 Grame
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
#ifdef __APPLE__
|
|
#include <TargetConditionals.h>
|
|
#endif
|
|
|
|
#include "JackAudioAdapter.h"
|
|
#ifndef MY_TARGET_OS_IPHONE
|
|
#include "JackLibSampleRateResampler.h"
|
|
#endif
|
|
#include "JackTime.h"
|
|
#include "JackError.h"
|
|
#include <stdio.h>
|
|
|
|
namespace Jack
|
|
{
|
|
|
|
#ifdef JACK_MONITOR
|
|
|
|
void MeasureTable::Write(int time1, int time2, float r1, float r2, int pos1, int pos2)
|
|
{
|
|
int pos = (++fCount) % TABLE_MAX;
|
|
fTable[pos].time1 = time1;
|
|
fTable[pos].time2 = time2;
|
|
fTable[pos].r1 = r1;
|
|
fTable[pos].r2 = r2;
|
|
fTable[pos].pos1 = pos1;
|
|
fTable[pos].pos2 = pos2;
|
|
}
|
|
|
|
void MeasureTable::Save(unsigned int fHostBufferSize, unsigned int fHostSampleRate, unsigned int fAdaptedSampleRate, unsigned int fAdaptedBufferSize)
|
|
{
|
|
FILE* file = fopen("JackAudioAdapter.log", "w");
|
|
|
|
int max = (fCount) % TABLE_MAX - 1;
|
|
for (int i = 1; i < max; i++) {
|
|
fprintf(file, "%d \t %d \t %d \t %f \t %f \t %d \t %d \n",
|
|
fTable[i].delta, fTable[i].time1, fTable[i].time2,
|
|
fTable[i].r1, fTable[i].r2, fTable[i].pos1, fTable[i].pos2);
|
|
}
|
|
fclose(file);
|
|
|
|
// No used for now
|
|
// Adapter timing 1
|
|
file = fopen("AdapterTiming1.plot", "w");
|
|
fprintf(file, "set multiplot\n");
|
|
fprintf(file, "set grid\n");
|
|
fprintf(file, "set title \"Audio adapter timing: host [rate = %.1f kHz buffer = %d frames] adapter [rate = %.1f kHz buffer = %d frames] \"\n"
|
|
,float(fHostSampleRate)/1000.f, fHostBufferSize, float(fAdaptedSampleRate)/1000.f, fAdaptedBufferSize);
|
|
fprintf(file, "set xlabel \"audio cycles\"\n");
|
|
fprintf(file, "set ylabel \"frames\"\n");
|
|
fprintf(file, "plot ");
|
|
fprintf(file, "\"JackAudioAdapter.log\" using 2 title \"Ringbuffer error\" with lines,");
|
|
fprintf(file, "\"JackAudioAdapter.log\" using 3 title \"Ringbuffer error with timing correction\" with lines");
|
|
|
|
fprintf(file, "\n unset multiplot\n");
|
|
fprintf(file, "set output 'AdapterTiming1.svg\n");
|
|
fprintf(file, "set terminal svg\n");
|
|
|
|
fprintf(file, "set multiplot\n");
|
|
fprintf(file, "set grid\n");
|
|
fprintf(file, "set title \"Audio adapter timing: host [rate = %.1f kHz buffer = %d frames] adapter [rate = %.1f kHz buffer = %d frames] \"\n"
|
|
,float(fHostSampleRate)/1000.f, fHostBufferSize, float(fAdaptedSampleRate)/1000.f, fAdaptedBufferSize);
|
|
fprintf(file, "set xlabel \"audio cycles\"\n");
|
|
fprintf(file, "set ylabel \"frames\"\n");
|
|
fprintf(file, "plot ");
|
|
fprintf(file, "\"JackAudioAdapter.log\" using 2 title \"Consumer interrupt period\" with lines,");
|
|
fprintf(file, "\"JackAudioAdapter.log\" using 3 title \"Producer interrupt period\" with lines\n");
|
|
fprintf(file, "unset multiplot\n");
|
|
fprintf(file, "unset output\n");
|
|
|
|
fclose(file);
|
|
|
|
// Adapter timing 2
|
|
file = fopen("AdapterTiming2.plot", "w");
|
|
fprintf(file, "set multiplot\n");
|
|
fprintf(file, "set grid\n");
|
|
fprintf(file, "set title \"Audio adapter timing: host [rate = %.1f kHz buffer = %d frames] adapter [rate = %.1f kHz buffer = %d frames] \"\n"
|
|
,float(fHostSampleRate)/1000.f, fHostBufferSize, float(fAdaptedSampleRate)/1000.f, fAdaptedBufferSize);
|
|
fprintf(file, "set xlabel \"audio cycles\"\n");
|
|
fprintf(file, "set ylabel \"resampling ratio\"\n");
|
|
fprintf(file, "plot ");
|
|
fprintf(file, "\"JackAudioAdapter.log\" using 4 title \"Ratio 1\" with lines,");
|
|
fprintf(file, "\"JackAudioAdapter.log\" using 5 title \"Ratio 2\" with lines");
|
|
|
|
fprintf(file, "\n unset multiplot\n");
|
|
fprintf(file, "set output 'AdapterTiming2.svg\n");
|
|
fprintf(file, "set terminal svg\n");
|
|
|
|
fprintf(file, "set multiplot\n");
|
|
fprintf(file, "set grid\n");
|
|
fprintf(file, "set title \"Audio adapter timing: host [rate = %.1f kHz buffer = %d frames] adapter [rate = %.1f kHz buffer = %d frames] \"\n"
|
|
,float(fHostSampleRate)/1000.f, fHostBufferSize, float(fAdaptedSampleRate)/1000.f, fAdaptedBufferSize);
|
|
fprintf(file, "set xlabel \"audio cycles\"\n");
|
|
fprintf(file, "set ylabel \"resampling ratio\"\n");
|
|
fprintf(file, "plot ");
|
|
fprintf(file, "\"JackAudioAdapter.log\" using 4 title \"Ratio 1\" with lines,");
|
|
fprintf(file, "\"JackAudioAdapter.log\" using 5 title \"Ratio 2\" with lines\n");
|
|
fprintf(file, "unset multiplot\n");
|
|
fprintf(file, "unset output\n");
|
|
|
|
fclose(file);
|
|
|
|
// Adapter timing 3
|
|
file = fopen("AdapterTiming3.plot", "w");
|
|
fprintf(file, "set multiplot\n");
|
|
fprintf(file, "set grid\n");
|
|
fprintf(file, "set title \"Audio adapter timing: host [rate = %.1f kHz buffer = %d frames] adapter [rate = %.1f kHz buffer = %d frames] \"\n"
|
|
,float(fHostSampleRate)/1000.f, fHostBufferSize, float(fAdaptedSampleRate)/1000.f, fAdaptedBufferSize);
|
|
fprintf(file, "set xlabel \"audio cycles\"\n");
|
|
fprintf(file, "set ylabel \"frames\"\n");
|
|
fprintf(file, "plot ");
|
|
fprintf(file, "\"JackAudioAdapter.log\" using 6 title \"Frames position in consumer ringbuffer\" with lines,");
|
|
fprintf(file, "\"JackAudioAdapter.log\" using 7 title \"Frames position in producer ringbuffer\" with lines");
|
|
|
|
fprintf(file, "\n unset multiplot\n");
|
|
fprintf(file, "set output 'AdapterTiming3.svg\n");
|
|
fprintf(file, "set terminal svg\n");
|
|
|
|
fprintf(file, "set multiplot\n");
|
|
fprintf(file, "set grid\n");
|
|
fprintf(file, "set title \"Audio adapter timing: host [rate = %.1f kHz buffer = %d frames] adapter [rate = %.1f kHz buffer = %d frames] \"\n"
|
|
,float(fHostSampleRate)/1000.f, fHostBufferSize, float(fAdaptedSampleRate)/1000.f, fAdaptedBufferSize);
|
|
fprintf(file, "set xlabel \"audio cycles\"\n");
|
|
fprintf(file, "set ylabel \"frames\"\n");
|
|
fprintf(file, "plot ");
|
|
fprintf(file, "\"JackAudioAdapter.log\" using 6 title \"Frames position in consumer ringbuffer\" with lines,");
|
|
fprintf(file, "\"JackAudioAdapter.log\" using 7 title \"Frames position in producer ringbuffer\" with lines\n");
|
|
fprintf(file, "unset multiplot\n");
|
|
fprintf(file, "unset output\n");
|
|
|
|
fclose(file);
|
|
}
|
|
|
|
#endif
|
|
|
|
void JackAudioAdapterInterface::GrowRingBufferSize()
|
|
{
|
|
fRingbufferCurSize *= 2;
|
|
}
|
|
|
|
void JackAudioAdapterInterface::AdaptRingBufferSize()
|
|
{
|
|
if (fHostBufferSize > fAdaptedBufferSize) {
|
|
fRingbufferCurSize = 4 * fHostBufferSize;
|
|
} else {
|
|
fRingbufferCurSize = 4 * fAdaptedBufferSize;
|
|
}
|
|
}
|
|
|
|
void JackAudioAdapterInterface::ResetRingBuffers()
|
|
{
|
|
if (fRingbufferCurSize > DEFAULT_RB_SIZE) {
|
|
fRingbufferCurSize = DEFAULT_RB_SIZE;
|
|
}
|
|
|
|
for (int i = 0; i < fCaptureChannels; i++) {
|
|
fCaptureRingBuffer[i]->Reset(fRingbufferCurSize);
|
|
}
|
|
for (int i = 0; i < fPlaybackChannels; i++) {
|
|
fPlaybackRingBuffer[i]->Reset(fRingbufferCurSize);
|
|
}
|
|
}
|
|
|
|
void JackAudioAdapterInterface::Reset()
|
|
{
|
|
ResetRingBuffers();
|
|
fRunning = false;
|
|
}
|
|
|
|
#ifdef MY_TARGET_OS_IPHONE
|
|
void JackAudioAdapterInterface::Create()
|
|
{}
|
|
#else
|
|
void JackAudioAdapterInterface::Create()
|
|
{
|
|
//ringbuffers
|
|
fCaptureRingBuffer = new JackResampler*[fCaptureChannels];
|
|
fPlaybackRingBuffer = new JackResampler*[fPlaybackChannels];
|
|
|
|
if (fAdaptative) {
|
|
AdaptRingBufferSize();
|
|
jack_info("Ringbuffer automatic adaptative mode size = %d frames", fRingbufferCurSize);
|
|
} else {
|
|
if (fRingbufferCurSize > DEFAULT_RB_SIZE) {
|
|
fRingbufferCurSize = DEFAULT_RB_SIZE;
|
|
}
|
|
jack_info("Fixed ringbuffer size = %d frames", fRingbufferCurSize);
|
|
}
|
|
|
|
for (int i = 0; i < fCaptureChannels; i++ ) {
|
|
fCaptureRingBuffer[i] = new JackLibSampleRateResampler(fQuality);
|
|
fCaptureRingBuffer[i]->Reset(fRingbufferCurSize);
|
|
}
|
|
for (int i = 0; i < fPlaybackChannels; i++ ) {
|
|
fPlaybackRingBuffer[i] = new JackLibSampleRateResampler(fQuality);
|
|
fPlaybackRingBuffer[i]->Reset(fRingbufferCurSize);
|
|
}
|
|
|
|
if (fCaptureChannels > 0) {
|
|
jack_log("ReadSpace = %ld", fCaptureRingBuffer[0]->ReadSpace());
|
|
}
|
|
if (fPlaybackChannels > 0) {
|
|
jack_log("WriteSpace = %ld", fPlaybackRingBuffer[0]->WriteSpace());
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void JackAudioAdapterInterface::Destroy()
|
|
{
|
|
for (int i = 0; i < fCaptureChannels; i++) {
|
|
delete(fCaptureRingBuffer[i]);
|
|
}
|
|
for (int i = 0; i < fPlaybackChannels; i++) {
|
|
delete (fPlaybackRingBuffer[i]);
|
|
}
|
|
|
|
delete[] fCaptureRingBuffer;
|
|
delete[] fPlaybackRingBuffer;
|
|
}
|
|
|
|
int JackAudioAdapterInterface::PushAndPull(float** inputBuffer, float** outputBuffer, unsigned int frames)
|
|
{
|
|
bool failure = false;
|
|
fRunning = true;
|
|
|
|
// Finer estimation of the position in the ringbuffer
|
|
int delta_frames = (fPullAndPushTime > 0) ? (int)((float(long(GetMicroSeconds() - fPullAndPushTime)) * float(fAdaptedSampleRate)) / 1000000.f) : 0;
|
|
|
|
double ratio = 1;
|
|
|
|
// TODO : done like this just to avoid crash when input only or output only...
|
|
if (fCaptureChannels > 0) {
|
|
ratio = fPIControler.GetRatio(fCaptureRingBuffer[0]->GetError() - delta_frames);
|
|
} else if (fPlaybackChannels > 0) {
|
|
ratio = fPIControler.GetRatio(fPlaybackRingBuffer[0]->GetError() - delta_frames);
|
|
}
|
|
|
|
#ifdef JACK_MONITOR
|
|
if (fCaptureRingBuffer && fCaptureRingBuffer[0] != NULL)
|
|
fTable.Write(fCaptureRingBuffer[0]->GetError(), fCaptureRingBuffer[0]->GetError() - delta_frames, ratio, 1/ratio, fCaptureRingBuffer[0]->ReadSpace(), fCaptureRingBuffer[0]->ReadSpace());
|
|
#endif
|
|
|
|
// Push/pull from ringbuffer
|
|
for (int i = 0; i < fCaptureChannels; i++) {
|
|
fCaptureRingBuffer[i]->SetRatio(ratio);
|
|
if (inputBuffer[i]) {
|
|
if (fCaptureRingBuffer[i]->WriteResample(inputBuffer[i], frames) < frames) {
|
|
failure = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < fPlaybackChannels; i++) {
|
|
fPlaybackRingBuffer[i]->SetRatio(1/ratio);
|
|
if (outputBuffer[i]) {
|
|
if (fPlaybackRingBuffer[i]->ReadResample(outputBuffer[i], frames) < frames) {
|
|
failure = true;
|
|
}
|
|
}
|
|
}
|
|
// Reset all ringbuffers in case of failure
|
|
if (failure) {
|
|
jack_error("JackAudioAdapterInterface::PushAndPull ringbuffer failure... reset");
|
|
if (fAdaptative) {
|
|
GrowRingBufferSize();
|
|
jack_info("Ringbuffer size = %d frames", fRingbufferCurSize);
|
|
}
|
|
ResetRingBuffers();
|
|
return -1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int JackAudioAdapterInterface::PullAndPush(float** inputBuffer, float** outputBuffer, unsigned int frames)
|
|
{
|
|
fPullAndPushTime = GetMicroSeconds();
|
|
if (!fRunning) {
|
|
return 0;
|
|
}
|
|
|
|
int res = 0;
|
|
|
|
// Push/pull from ringbuffer
|
|
for (int i = 0; i < fCaptureChannels; i++) {
|
|
if (inputBuffer[i]) {
|
|
if (fCaptureRingBuffer[i]->Read(inputBuffer[i], frames) < frames) {
|
|
res = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < fPlaybackChannels; i++) {
|
|
if (outputBuffer[i]) {
|
|
if (fPlaybackRingBuffer[i]->Write(outputBuffer[i], frames) < frames) {
|
|
res = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
int JackAudioAdapterInterface::SetHostBufferSize(jack_nframes_t buffer_size)
|
|
{
|
|
fHostBufferSize = buffer_size;
|
|
if (fAdaptative) {
|
|
AdaptRingBufferSize();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int JackAudioAdapterInterface::SetAdaptedBufferSize(jack_nframes_t buffer_size)
|
|
{
|
|
fAdaptedBufferSize = buffer_size;
|
|
if (fAdaptative) {
|
|
AdaptRingBufferSize();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int JackAudioAdapterInterface::SetBufferSize(jack_nframes_t buffer_size)
|
|
{
|
|
SetHostBufferSize(buffer_size);
|
|
SetAdaptedBufferSize(buffer_size);
|
|
return 0;
|
|
}
|
|
|
|
int JackAudioAdapterInterface::SetHostSampleRate(jack_nframes_t sample_rate)
|
|
{
|
|
fHostSampleRate = sample_rate;
|
|
fPIControler.Init(double(fHostSampleRate) / double(fAdaptedSampleRate));
|
|
return 0;
|
|
}
|
|
|
|
int JackAudioAdapterInterface::SetAdaptedSampleRate(jack_nframes_t sample_rate)
|
|
{
|
|
fAdaptedSampleRate = sample_rate;
|
|
fPIControler.Init(double(fHostSampleRate) / double(fAdaptedSampleRate));
|
|
return 0;
|
|
}
|
|
|
|
int JackAudioAdapterInterface::SetSampleRate(jack_nframes_t sample_rate)
|
|
{
|
|
SetHostSampleRate(sample_rate);
|
|
SetAdaptedSampleRate(sample_rate);
|
|
return 0;
|
|
}
|
|
|
|
void JackAudioAdapterInterface::SetInputs(int inputs)
|
|
{
|
|
jack_log("JackAudioAdapterInterface::SetInputs %d", inputs);
|
|
fCaptureChannels = inputs;
|
|
}
|
|
|
|
void JackAudioAdapterInterface::SetOutputs(int outputs)
|
|
{
|
|
jack_log("JackAudioAdapterInterface::SetOutputs %d", outputs);
|
|
fPlaybackChannels = outputs;
|
|
}
|
|
|
|
int JackAudioAdapterInterface::GetInputs()
|
|
{
|
|
//jack_log("JackAudioAdapterInterface::GetInputs %d", fCaptureChannels);
|
|
return fCaptureChannels;
|
|
}
|
|
|
|
int JackAudioAdapterInterface::GetOutputs()
|
|
{
|
|
//jack_log ("JackAudioAdapterInterface::GetOutputs %d", fPlaybackChannels);
|
|
return fPlaybackChannels;
|
|
}
|
|
|
|
|
|
} // namespace
|