diff --git a/plugins/feature/demodanalyzer/demodanalyzer.cpp b/plugins/feature/demodanalyzer/demodanalyzer.cpp index bcc183f80..5e9bf95b3 100644 --- a/plugins/feature/demodanalyzer/demodanalyzer.cpp +++ b/plugins/feature/demodanalyzer/demodanalyzer.cpp @@ -131,6 +131,7 @@ void DemodAnalyzer::start() DemodAnalyzerWorker::MsgConfigureDemodAnalyzerWorker *msg = DemodAnalyzerWorker::MsgConfigureDemodAnalyzerWorker::create(m_settings, QList(), true); m_worker->getInputMessageQueue()->push(msg); + m_worker->applySampleRate(m_sampleRate); if (m_dataPipe) { diff --git a/plugins/feature/demodanalyzer/demodanalyzerworker.cpp b/plugins/feature/demodanalyzer/demodanalyzerworker.cpp index fdf29cfa4..cd57840e6 100644 --- a/plugins/feature/demodanalyzer/demodanalyzerworker.cpp +++ b/plugins/feature/demodanalyzer/demodanalyzerworker.cpp @@ -29,6 +29,7 @@ MESSAGE_CLASS_DEFINITION(DemodAnalyzerWorker::MsgConnectFifo, Message) DemodAnalyzerWorker::DemodAnalyzerWorker() : m_dataFifo(nullptr), + m_sinkSampleRate(0), m_msgQueueToFeature(nullptr), m_sampleBufferSize(0), m_wavFileRecord(nullptr), diff --git a/sdrbase/dsp/wavfilerecord.cpp b/sdrbase/dsp/wavfilerecord.cpp index b201f2c77..a052b4c27 100644 --- a/sdrbase/dsp/wavfilerecord.cpp +++ b/sdrbase/dsp/wavfilerecord.cpp @@ -204,6 +204,126 @@ bool WavFileRecord::startRecording() return true; } +void addString(QByteArray& buffer, const QString& type, const QString text) +{ + buffer.append(type.toUtf8(), 4); + + QByteArray s = text.toUtf8(); + s.append('\0'); // Strings should be null terminated + if (s.size() & 1) { + s.append('\0'); + } + + quint16 textSize = s.size(); + buffer.append(textSize & 0xff); + buffer.append((textSize >> 8) & 0xff); + buffer.append((textSize >> 16) & 0xff); + buffer.append((textSize >> 24) & 0xff); + + buffer.append(s, textSize); +} + +void addTag(QByteArray& buffer, const QString& id, const QString text) +{ + buffer.append(id.toUtf8(), 4); + QByteArray s = text.toUtf8(); + int size = s.size() + 1; + buffer.append((size >> 24) & 0xff); // MSB first + buffer.append((size >> 16) & 0xff); + buffer.append((size >> 8) & 0xff); + buffer.append(size & 0xff); + + buffer.append((char)0); // flags + buffer.append((char)0); // flags + + buffer.append((char)0); // text encoding (0 ISO-8859-1, 1 Unicode) + buffer.append(s); +} + +void WavFileRecord::setMetaData(const QString& trackTitle, const QString& album, const QString& artist) +{ + m_trackTitle = trackTitle; + m_album = album; + m_artist = artist; +} + +bool WavFileRecord::hasMetaData() const +{ + return !m_trackTitle.isEmpty() || !m_album.isEmpty() || !m_artist.isEmpty(); +} + +void WavFileRecord::writeInfoList() +{ + QByteArray meta; + + meta.append("INFO", 4); + + if (!m_trackTitle.isEmpty()) { + addString(meta, "INAM", m_trackTitle); + } + if (!m_album.isEmpty()) { + addString(meta, "IPRD", m_album); + } + if (!m_artist.isEmpty()) { + addString(meta, "IART", m_artist); + } + + struct Chunk list; + list.m_id[0] = 'L'; + list.m_id[1] = 'I'; + list.m_id[2] = 'S'; + list.m_id[3] = 'T'; + list.m_size = meta.size(); + + m_sampleFile.write((char*)&list, sizeof(Chunk)); + m_sampleFile.write((char*)meta.data(), meta.size()); +} + +// ID3 v2.3 +// https://mutagen-specs.readthedocs.io/en/latest/id3/id3v2.3.0.html +void WavFileRecord::writeID3() +{ + QByteArray meta; + + QByteArray textInfo; + if (!m_artist.isEmpty()) { + addTag(textInfo, "TPE1", m_artist); + } + if (!m_trackTitle.isEmpty()) { + addTag(textInfo, "TIT2", m_trackTitle); + } + if (!m_album.isEmpty()) { + addTag(textInfo, "TALB", m_album); + } + + meta.append("ID3", 3); + meta.append((char)3); // .3 version + meta.append((char)0); // revision + meta.append((char)0); // flags + + int textInfoSize = textInfo.size(); + meta.append((textInfoSize >> 24) & 0xff); // MSB first + meta.append((textInfoSize >> 16) & 0xff); + meta.append((textInfoSize >> 8) & 0xff); + meta.append(textInfoSize & 0xff); + + meta.append(textInfo); + + if (meta.size() & 1) { + meta.append((char)0); + } + + struct Chunk id3; + id3.m_id[0] = 'i'; + id3.m_id[1] = 'd'; + id3.m_id[2] = '3'; + id3.m_id[3] = ' '; + id3.m_size = meta.size(); + + m_sampleFile.write((char*)&id3, sizeof(Chunk)); + m_sampleFile.write((char*)meta.data(), meta.size()); +} + bool WavFileRecord::stopRecording() { #ifdef ANDROID @@ -213,6 +333,20 @@ bool WavFileRecord::stopRecording() #endif { qDebug() << "WavFileRecord::stopRecording"; + +#ifdef ANDROID + long dataSize = (long)m_sampleFile.size(); +#else + long dataSize = m_sampleFile.tellp(); +#endif + + // Write meta data + if (hasMetaData()) + { + writeInfoList(); + writeID3(); + } + // Fix up chunk sizes #ifdef ANDROID long fileSize = (long)m_sampleFile.size(); @@ -228,7 +362,7 @@ bool WavFileRecord::stopRecording() #else m_sampleFile.seekp(offsetof(Header, m_dataHeader.m_size)); #endif - size = fileSize - sizeof(Header); + size = dataSize - sizeof(Header); m_sampleFile.write((char *)&size, 4); m_sampleFile.close(); m_recordOn = false; diff --git a/sdrbase/dsp/wavfilerecord.h b/sdrbase/dsp/wavfilerecord.h index b05ac2f61..62c68d953 100644 --- a/sdrbase/dsp/wavfilerecord.h +++ b/sdrbase/dsp/wavfilerecord.h @@ -114,6 +114,9 @@ public: virtual bool stopRecording() override; virtual bool isRecording() const override { return m_recordOn; } + void setMetaData(const QString& trackTitle="", const QString& album="", const QString& artist=""); + bool hasMetaData() const; + static bool readHeader(std::ifstream& samplefile, Header& header, bool check=true); static bool readHeader(QFile& samplefile, Header& header); static void writeHeader(std::ofstream& samplefile, Header& header); @@ -140,7 +143,13 @@ private: qint64 m_msShift; int m_nbChannels; + QString m_trackTitle; + QString m_album; + QString m_artist; + void writeHeader(); + void writeInfoList(); + void writeID3(); static bool checkHeader(Header& header); };