mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-08-22 14:06:34 -04:00
330 lines
9.6 KiB
C++
330 lines
9.6 KiB
C++
///////////////////////////////////////////////////////////////////////////////////
|
|
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
|
// //
|
|
// 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 //
|
|
// (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 V3 for more details. //
|
|
// //
|
|
// You should have received a copy of the GNU General Public License //
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "filesourcesource.h"
|
|
#include "filesourcereport.h"
|
|
|
|
#if (defined _WIN32_) || (defined _MSC_VER)
|
|
#include "windows_time.h"
|
|
#include <stdint.h>
|
|
#else
|
|
#include <sys/time.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <QDebug>
|
|
|
|
#include "dsp/dspcommands.h"
|
|
#include "dsp/devicesamplesink.h"
|
|
#include "dsp/hbfilterchainconverter.h"
|
|
#include "dsp/filerecord.h"
|
|
#include "dsp/wavfilerecord.h"
|
|
#include "util/db.h"
|
|
|
|
FileSourceSource::FileSourceSource() :
|
|
m_fileName("..."),
|
|
m_sampleSize(0),
|
|
m_centerFrequency(0),
|
|
m_frequencyOffset(0),
|
|
m_fileSampleRate(0),
|
|
m_samplesCount(0),
|
|
m_sampleRate(0),
|
|
m_deviceSampleRate(0),
|
|
m_recordLengthMuSec(0),
|
|
m_startingTimeStamp(0),
|
|
m_running(false),
|
|
m_guiMessageQueue(nullptr)
|
|
{
|
|
m_linearGain = 1.0f;
|
|
m_magsq = 0.0f;
|
|
m_magsqSum = 0.0f;
|
|
m_magsqPeak = 0.0f;
|
|
m_magsqCount = 0;
|
|
}
|
|
|
|
FileSourceSource::~FileSourceSource()
|
|
{
|
|
}
|
|
|
|
void FileSourceSource::pull(SampleVector::iterator begin, unsigned int nbSamples)
|
|
{
|
|
std::for_each(
|
|
begin,
|
|
begin + nbSamples,
|
|
[this](Sample& s) {
|
|
pullOne(s);
|
|
}
|
|
);
|
|
}
|
|
|
|
void FileSourceSource::pullOne(Sample& sample)
|
|
{
|
|
Real re;
|
|
Real im;
|
|
|
|
struct Sample16
|
|
{
|
|
int16_t real;
|
|
int16_t imag;
|
|
};
|
|
|
|
struct Sample24
|
|
{
|
|
int32_t real;
|
|
int32_t imag;
|
|
};
|
|
|
|
if (!m_running)
|
|
{
|
|
re = 0;
|
|
im = 0;
|
|
}
|
|
else if (m_sampleSize == 16)
|
|
{
|
|
Sample16 sample16;
|
|
m_ifstream.read(reinterpret_cast<char*>(&sample16), sizeof(Sample16));
|
|
|
|
if (m_ifstream.eof()) {
|
|
handleEOF();
|
|
} else {
|
|
m_samplesCount++;
|
|
}
|
|
|
|
// scale to +/-1.0
|
|
re = (sample16.real * m_linearGain) / 32760.0f;
|
|
im = (sample16.imag * m_linearGain) / 32760.0f;
|
|
}
|
|
else if (m_sampleSize == 24)
|
|
{
|
|
Sample24 sample24;
|
|
m_ifstream.read(reinterpret_cast<char*>(&sample24), sizeof(Sample24));
|
|
|
|
if (m_ifstream.eof()) {
|
|
handleEOF();
|
|
} else {
|
|
m_samplesCount++;
|
|
}
|
|
|
|
// scale to +/-1.0
|
|
re = (sample24.real * m_linearGain) / 8388608.0f;
|
|
im = (sample24.imag * m_linearGain) / 8388608.0f;
|
|
}
|
|
else
|
|
{
|
|
re = 0;
|
|
im = 0;
|
|
}
|
|
|
|
|
|
if (SDR_TX_SAMP_SZ == 16)
|
|
{
|
|
sample.setReal(re * 32768.0f);
|
|
sample.setImag(im * 32768.0f);
|
|
}
|
|
else if (SDR_TX_SAMP_SZ == 24)
|
|
{
|
|
sample.setReal(re * 8388608.0f);
|
|
sample.setImag(im * 8388608.0f);
|
|
}
|
|
else
|
|
{
|
|
sample.setReal(0);
|
|
sample.setImag(0);
|
|
}
|
|
|
|
Real magsq = re*re + im*im;
|
|
m_movingAverage(magsq);
|
|
m_magsq = m_movingAverage.asDouble();
|
|
m_magsqSum += magsq;
|
|
|
|
if (magsq > m_magsqPeak) {
|
|
m_magsqPeak = magsq;
|
|
}
|
|
|
|
m_magsqCount++;
|
|
}
|
|
|
|
void FileSourceSource::openFileStream(const QString& fileName)
|
|
{
|
|
m_fileName = fileName;
|
|
|
|
if (m_ifstream.is_open()) {
|
|
m_ifstream.close();
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
m_ifstream.open(m_fileName.toStdWString().c_str(), std::ios::binary | std::ios::ate);
|
|
#else
|
|
m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate);
|
|
#endif
|
|
quint64 fileSize = m_ifstream.tellg();
|
|
m_samplesCount = 0;
|
|
|
|
if (m_fileName.endsWith(".wav"))
|
|
{
|
|
WavFileRecord::Header header;
|
|
m_ifstream.seekg(0, std::ios_base::beg);
|
|
bool headerOK = WavFileRecord::readHeader(m_ifstream, header);
|
|
m_fileSampleRate = header.m_sampleRate;
|
|
if (header.m_auxiHeader.m_size > 0)
|
|
{
|
|
// Some WAV files written by SDR tools have auxi header
|
|
m_centerFrequency = header.m_auxi.m_centerFreq;
|
|
m_startingTimeStamp = header.getStartTime().toMSecsSinceEpoch() / 1000;
|
|
}
|
|
else
|
|
{
|
|
// Attempt to extract start time and frequency from filename
|
|
QDateTime startTime;
|
|
if (WavFileRecord::getStartTime(m_fileName, startTime)) {
|
|
m_startingTimeStamp = startTime.toMSecsSinceEpoch() / 1000;
|
|
}
|
|
WavFileRecord::getCenterFrequency(m_fileName, m_centerFrequency);
|
|
}
|
|
m_sampleSize = header.m_bitsPerSample;
|
|
|
|
if (headerOK && (m_fileSampleRate > 0) && (m_sampleSize > 0))
|
|
{
|
|
m_recordLengthMuSec = ((fileSize - m_ifstream.tellg()) * 1000000UL) / ((m_sampleSize == 24 ? 8 : 4) * m_fileSampleRate);
|
|
}
|
|
else
|
|
{
|
|
qCritical("FileSourceSource::openFileStream: invalid .wav file");
|
|
m_recordLengthMuSec = 0;
|
|
}
|
|
|
|
if (getMessageQueueToGUI())
|
|
{
|
|
FileSourceReport::MsgReportHeaderCRC *report = FileSourceReport::MsgReportHeaderCRC::create(headerOK);
|
|
getMessageQueueToGUI()->push(report);
|
|
}
|
|
}
|
|
else if (fileSize > sizeof(FileRecord::Header))
|
|
{
|
|
FileRecord::Header header;
|
|
m_ifstream.seekg(0,std::ios_base::beg);
|
|
bool crcOK = FileRecord::readHeader(m_ifstream, header);
|
|
m_fileSampleRate = header.sampleRate;
|
|
m_centerFrequency = header.centerFrequency;
|
|
m_startingTimeStamp = header.startTimeStamp;
|
|
m_sampleSize = header.sampleSize;
|
|
QString crcHex = QString("%1").arg(header.crc32 , 0, 16);
|
|
|
|
if (crcOK)
|
|
{
|
|
qDebug("FileSourceSource::openFileStream: CRC32 OK for header: %s", qPrintable(crcHex));
|
|
m_recordLengthMuSec = ((fileSize - sizeof(FileRecord::Header)) * 1000000UL) / ((m_sampleSize == 24 ? 8 : 4) * m_fileSampleRate);
|
|
}
|
|
else
|
|
{
|
|
qCritical("FileSourceSource::openFileStream: bad CRC32 for header: %s", qPrintable(crcHex));
|
|
m_recordLengthMuSec = 0;
|
|
}
|
|
|
|
if (getMessageQueueToGUI())
|
|
{
|
|
FileSourceReport::MsgReportHeaderCRC *report = FileSourceReport::MsgReportHeaderCRC::create(crcOK);
|
|
getMessageQueueToGUI()->push(report);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_recordLengthMuSec = 0;
|
|
}
|
|
|
|
qDebug() << "FileSourceSource::openFileStream: " << m_fileName.toStdString().c_str()
|
|
<< " fileSize: " << fileSize << " bytes"
|
|
<< " length: " << m_recordLengthMuSec << " microseconds"
|
|
<< " sample rate: " << m_fileSampleRate << " S/s"
|
|
<< " center frequency: " << m_centerFrequency << " Hz"
|
|
<< " sample size: " << m_sampleSize << " bits"
|
|
<< " starting TS: " << m_startingTimeStamp << "s";
|
|
|
|
if (getMessageQueueToGUI())
|
|
{
|
|
FileSourceReport::MsgReportFileSourceStreamData *report = FileSourceReport::MsgReportFileSourceStreamData::create(m_fileSampleRate,
|
|
m_sampleSize,
|
|
m_centerFrequency,
|
|
m_startingTimeStamp,
|
|
m_recordLengthMuSec); // file stream data
|
|
getMessageQueueToGUI()->push(report);
|
|
}
|
|
|
|
if (m_recordLengthMuSec == 0) {
|
|
m_ifstream.close();
|
|
}
|
|
}
|
|
|
|
void FileSourceSource::seekFileStream(int seekMillis)
|
|
{
|
|
if ((m_ifstream.is_open()) && !m_running)
|
|
{
|
|
quint64 seekPoint = ((m_recordLengthMuSec * seekMillis) / 1000) * m_fileSampleRate;
|
|
seekPoint /= 1000000UL;
|
|
m_samplesCount = seekPoint;
|
|
seekPoint *= (m_sampleSize == 24 ? 8 : 4); // + sizeof(FileRecord::Header)
|
|
m_ifstream.clear();
|
|
m_ifstream.seekg(seekPoint + sizeof(FileRecord::Header), std::ios::beg);
|
|
}
|
|
}
|
|
|
|
void FileSourceSource::handleEOF()
|
|
{
|
|
if (!m_ifstream.is_open()) {
|
|
return;
|
|
}
|
|
|
|
if (getMessageQueueToGUI())
|
|
{
|
|
FileSourceReport::MsgReportFileSourceStreamTiming *report = FileSourceReport::MsgReportFileSourceStreamTiming::create(getSamplesCount());
|
|
getMessageQueueToGUI()->push(report);
|
|
}
|
|
|
|
if (m_settings.m_loop)
|
|
{
|
|
m_ifstream.clear();
|
|
m_ifstream.seekg(0, std::ios::beg);
|
|
m_samplesCount = 0;
|
|
}
|
|
else
|
|
{
|
|
if (getMessageQueueToGUI())
|
|
{
|
|
FileSourceReport::MsgPlayPause *report = FileSourceReport::MsgPlayPause::create(false);
|
|
getMessageQueueToGUI()->push(report);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FileSourceSource::applySettings(const FileSourceSettings& settings, bool force)
|
|
{
|
|
qDebug() << "FileSourceSource::applySettings:"
|
|
<< "m_fileName:" << settings.m_fileName
|
|
<< "m_loop:" << settings.m_loop
|
|
<< "m_gainDB:" << settings.m_gainDB
|
|
<< "m_log2Interp:" << settings.m_log2Interp
|
|
<< "m_filterChainHash:" << settings.m_filterChainHash
|
|
<< " force: " << force;
|
|
|
|
|
|
if ((m_settings.m_gainDB != settings.m_gainDB) || force) {
|
|
m_linearGain = CalcDb::powerFromdB(settings.m_gainDB/2.0); // Divide by two for power gain to voltage gain conversion
|
|
}
|
|
|
|
m_settings = settings;
|
|
}
|