mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-31 04:50:29 -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;
 | |
| }
 |