mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-25 05:38:46 -05:00
Tool to exercise QAudioInput
The tool record_time_signal is designed to measure the performance of QAudioInput. The intended use is to record a short period of live audio from an on-air time signal of known good quaility, the basic required parameters are an audio input device, an output file name (.WAV), a start second in a minute, and a duration in seconds. So for example to record the time signal ticks and fast data at the top of the minute: $ record_time_signal -o wwv.wav -s 55 -d 15 will record 15s of audio at 48000Hz sample rate, stereo, from the default audio input device, starting at second 55. This will use a separate timer to stop the recording which is likely to leave the output file a little short due to buffer latency. The buffer size can be adjusted using the '-b <buffered-frames>' option. The tool also supoorts a different mechanism to time the recording which uses the audio progress via a notify signal. This should ensure at least the requested duration is recorded The shorter the notify interval the closer teh final size shoould be to the requested duration. Use the '-d <interval-ms>' option to adjust the notify interval. $ record_time_signal -o wwv.wav -s 55 -d 15 -n 100 Non-default audio devices can be selected, use the '-I' option to list the available input devices with an index number that can be used to select the device using the 'R <device-number>' option. Other options are available, use '-h' for details.
This commit is contained in:
parent
10b3debe8f
commit
3a3af42cc6
231
Audio/tools/record_time_signal.cpp
Normal file
231
Audio/tools/record_time_signal.cpp
Normal file
@ -0,0 +1,231 @@
|
||||
#include <iostream>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include <locale.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QTextStream>
|
||||
#include <QCommandLineParser>
|
||||
#include <QCommandLineOption>
|
||||
#include <QStringList>
|
||||
#include <QFileInfo>
|
||||
#include <QAudioFormat>
|
||||
#include <QAudioDeviceInfo>
|
||||
#include <QAudioInput>
|
||||
#include <QTimer>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "revision_utils.hpp"
|
||||
#include "Audio/BWFFile.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
QTextStream qtout {stdout};
|
||||
}
|
||||
|
||||
class Recorder final
|
||||
: public QObject
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
Recorder (int start, int duration, QString const& output, QAudioDeviceInfo const& source_device, QAudioFormat const& format, int notify_interval, int buffer_size)
|
||||
: source_ {source_device, format}
|
||||
, notify_interval_ {notify_interval}
|
||||
, output_ {format, output}
|
||||
, duration_ {duration}
|
||||
{
|
||||
if (!output_.open (BWFFile::WriteOnly)) throw std::invalid_argument {QString {"cannot open output file \"%1\""}.arg (output).toStdString ()};
|
||||
|
||||
if (buffer_size) source_.setBufferSize (format.bytesForFrames (buffer_size));
|
||||
if (notify_interval_)
|
||||
{
|
||||
source_.setNotifyInterval (notify_interval);
|
||||
connect (&source_, &QAudioInput::notify, this, &Recorder::notify);
|
||||
}
|
||||
|
||||
QTimer::singleShot (int ((((start - (QDateTime::currentMSecsSinceEpoch () / 1000) % 60) + 60) % 60) * 1000), Qt::PreciseTimer, this, &Recorder::start);
|
||||
}
|
||||
|
||||
Q_SIGNAL void done ();
|
||||
|
||||
private:
|
||||
Q_SLOT void start ()
|
||||
{
|
||||
qtout << "started recording at " << QDateTime::currentDateTimeUtc ().toString ("hh:mm:ss.zzz UTC") << endl;
|
||||
source_.start (&output_);
|
||||
if (!notify_interval_) QTimer::singleShot (duration_ * 1000, Qt::PreciseTimer, this, &Recorder::stop);
|
||||
qtout << QString {"buffer size used is: %1"}.arg (source_.bufferSize ()) << endl;
|
||||
}
|
||||
|
||||
Q_SLOT void notify ()
|
||||
{
|
||||
auto length = source_.elapsedUSecs ();
|
||||
qtout << QString {"%1 US recorded\r"}.arg (length) << flush;
|
||||
if (length >= duration_ * 1000 * 1000) stop ();
|
||||
}
|
||||
|
||||
Q_SLOT void stop ()
|
||||
{
|
||||
auto length = source_.elapsedUSecs ();
|
||||
source_.stop ();
|
||||
qtout << QString {"%1 uS recorded "}.arg (length) << '(' << source_.format ().framesForBytes (output_.size ()) << " frames recorded)\n";
|
||||
qtout << "stopped recording at " << QDateTime::currentDateTimeUtc ().toString ("hh:mm:ss.zzz UTC") << endl;
|
||||
Q_EMIT done ();
|
||||
}
|
||||
|
||||
QAudioInput source_;
|
||||
int notify_interval_;
|
||||
BWFFile output_;
|
||||
int duration_;
|
||||
};
|
||||
|
||||
#include "record_time_signal.moc"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication app {argc, argv};
|
||||
try
|
||||
{
|
||||
::setlocale (LC_NUMERIC, "C"); // ensure number forms are in
|
||||
// consistent format, do this
|
||||
// after instantiating
|
||||
// QApplication so that Qt has
|
||||
// correct l18n
|
||||
|
||||
// Override programs executable basename as application name.
|
||||
app.setApplicationName ("WSJT-X Record Time Signal");
|
||||
app.setApplicationVersion (version ());
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription (
|
||||
"\nTool to determine and experiment with QAudioInput latencies\n\n"
|
||||
"\tUse the -I option to list available recording device numbers\n"
|
||||
);
|
||||
auto help_option = parser.addHelpOption ();
|
||||
auto version_option = parser.addVersionOption ();
|
||||
|
||||
parser.addOptions ({
|
||||
{{"I", "list-audio-inputs"},
|
||||
app.translate ("main", "List the available audio input devices")},
|
||||
{{"s", "start-time"},
|
||||
app.translate ("main", "Record from <start-time> seconds"),
|
||||
app.translate ("main", "start-time")},
|
||||
{{"d", "duration"},
|
||||
app.translate ("main", "Recording <duration> seconds"),
|
||||
app.translate ("main", "duration")},
|
||||
{{"o", "output"},
|
||||
app.translate ("main", "Save output as <output-file>"),
|
||||
app.translate ("main", "output-file")},
|
||||
{{"f", "force"},
|
||||
app.translate ("main", "Overwrite existing file")},
|
||||
{{"r", "sample-rate"},
|
||||
app.translate ("main", "Record at <sample-rate>"),
|
||||
app.translate ("main", "sample-rate")},
|
||||
{{"c", "num-channels"},
|
||||
app.translate ("main", "Record <num> channels"),
|
||||
app.translate ("main", "num")},
|
||||
{{"R", "recording-device-number"},
|
||||
app.translate ("main", "Record from <device-number>"),
|
||||
app.translate ("main", "device-number")},
|
||||
{{"n", "notify-interval"},
|
||||
app.translate ("main", "use notify signals every <interval> milliseconds, zero to use a timer"),
|
||||
app.translate ("main", "interval")},
|
||||
{{"b", "buffer-size"},
|
||||
app.translate ("main", "audio buffer size <frames>"),
|
||||
app.translate ("main", "frames")},
|
||||
});
|
||||
parser.process (app);
|
||||
|
||||
auto input_devices = QAudioDeviceInfo::availableDevices (QAudio::AudioInput);
|
||||
if (parser.isSet ("I"))
|
||||
{
|
||||
int n {0};
|
||||
for (auto const& device : input_devices)
|
||||
{
|
||||
qtout << ++n << " - [" << device.deviceName () << ']' << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ok;
|
||||
int start = parser.value ("s").toInt (&ok);
|
||||
if (!ok) throw std::invalid_argument {"start time not a number"};
|
||||
int duration = parser.value ("d").toInt (&ok);
|
||||
if (!ok) throw std::invalid_argument {"duration not a number"};
|
||||
int sample_rate {48000};
|
||||
if (parser.isSet ("r"))
|
||||
{
|
||||
sample_rate = parser.value ("r").toInt (&ok);
|
||||
if (!ok) throw std::invalid_argument {"sample rate not a number"};
|
||||
}
|
||||
int num_channels {2};
|
||||
if (parser.isSet ("c"))
|
||||
{
|
||||
num_channels = parser.value ("c").toInt (&ok);
|
||||
if (!ok) throw std::invalid_argument {"channel count not a number"};
|
||||
}
|
||||
int notify_interval {0};
|
||||
if (parser.isSet ("n"))
|
||||
{
|
||||
notify_interval = parser.value ("n").toInt (&ok);
|
||||
if (!ok) throw std::invalid_argument {"notify interval not a number"};
|
||||
}
|
||||
int buffer_size {0};
|
||||
if (parser.isSet ("b"))
|
||||
{
|
||||
buffer_size = parser.value ("b").toInt (&ok);
|
||||
if (!ok) throw std::invalid_argument {"buffer size not a number"};
|
||||
}
|
||||
int input_device {0};
|
||||
if (parser.isSet ("R"))
|
||||
{
|
||||
input_device = parser.value ("R").toInt (&ok);
|
||||
if (!ok || 0 >= input_device || input_device > input_devices.size ())
|
||||
{
|
||||
throw std::invalid_argument {"invalid recording device"};
|
||||
}
|
||||
}
|
||||
if (!parser.isSet ("o")) throw std::invalid_argument {"output file required"};
|
||||
QFileInfo ofi {parser.value ("o")};
|
||||
if (!ofi.suffix ().size () && ofi.fileName ()[ofi.fileName ().size () - 1] != QChar {'.'})
|
||||
{
|
||||
ofi.setFile (ofi.filePath () + ".wav");
|
||||
}
|
||||
if (!parser.isSet ("f") && ofi.isFile ())
|
||||
{
|
||||
throw std::invalid_argument {"set the `-force' option to overwrite an existing output file"};
|
||||
}
|
||||
|
||||
QAudioFormat audio_format;
|
||||
audio_format.setSampleRate (sample_rate);
|
||||
audio_format.setChannelCount (num_channels);
|
||||
audio_format.setSampleSize (16);
|
||||
audio_format.setSampleType (QAudioFormat::SignedInt);
|
||||
audio_format.setCodec ("audio/pcm");
|
||||
|
||||
auto source = input_device ? input_devices[input_device] : QAudioDeviceInfo::defaultInputDevice ();
|
||||
if (!source.isFormatSupported (audio_format))
|
||||
{
|
||||
qtout << "warning, requested format not supported, using nearest" << endl;
|
||||
audio_format = source.nearestFormat (audio_format);
|
||||
}
|
||||
|
||||
// run the application
|
||||
Recorder record {start, duration, ofi.filePath (), source, audio_format, notify_interval, buffer_size};
|
||||
QObject::connect (&record, &Recorder::done, &app, &QCoreApplication::quit);
|
||||
return app.exec();
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
std::cerr << "Error: " << e.what () << '\n';
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::cerr << "Unexpected fatal error\n";
|
||||
throw; // hoping the runtime might tell us more about the exception
|
||||
}
|
||||
return -1;
|
||||
}
|
@ -1289,6 +1289,9 @@ target_link_libraries (ft4sim_mult wsjt_fort wsjt_cxx)
|
||||
add_executable (ft4d lib/ft4/ft4d.f90 wsjtx.rc)
|
||||
target_link_libraries (ft4d wsjt_fort wsjt_cxx)
|
||||
|
||||
add_executable (record_time_signal Audio/tools/record_time_signal.cpp)
|
||||
target_link_libraries (record_time_signal wsjt_cxx wsjt_qtmm wsjt_qt)
|
||||
|
||||
endif(WSJT_BUILD_UTILS)
|
||||
|
||||
# build the main application
|
||||
|
Loading…
Reference in New Issue
Block a user