mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-11-04 05:30:32 -05: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;
 | 
						|
}
 |