diff --git a/doc/img/DATVDemod_plugin.png b/doc/img/DATVDemod_plugin.png
index 7fc5d75a2..2d55a97fa 100644
Binary files a/doc/img/DATVDemod_plugin.png and b/doc/img/DATVDemod_plugin.png differ
diff --git a/doc/img/DATVDemod_plugin.xcf b/doc/img/DATVDemod_plugin.xcf
index 66558097c..b9695cef8 100644
Binary files a/doc/img/DATVDemod_plugin.xcf and b/doc/img/DATVDemod_plugin.xcf differ
diff --git a/doc/img/DATVDemod_pluginDATV.png b/doc/img/DATVDemod_pluginDATV.png
index 1d634007c..5628b8fc2 100644
Binary files a/doc/img/DATVDemod_pluginDATV.png and b/doc/img/DATVDemod_pluginDATV.png differ
diff --git a/doc/img/DATVDemod_pluginDATV.xcf b/doc/img/DATVDemod_pluginDATV.xcf
index 6bc102150..1fb6070c5 100644
Binary files a/doc/img/DATVDemod_pluginDATV.xcf and b/doc/img/DATVDemod_pluginDATV.xcf differ
diff --git a/plugins/channelrx/demoddatv/datvdemod.h b/plugins/channelrx/demoddatv/datvdemod.h
index d57262d25..581a5692c 100644
--- a/plugins/channelrx/demoddatv/datvdemod.h
+++ b/plugins/channelrx/demoddatv/datvdemod.h
@@ -102,10 +102,12 @@ public:
void setCNRMeter(LevelMeterSignalDB *cnrMeter) { m_basebandSink->setCNRMeter(cnrMeter); }
void SetVideoRender(DATVideoRender *objScreen) { m_basebandSink->SetVideoRender(objScreen); }
DATVideostream *getVideoStream() { return m_basebandSink->getVideoStream(); }
+ DATVUDPStream *getUDPStream() { return m_basebandSink->getUDPStream(); }
bool audioActive() { return m_basebandSink->audioActive(); }
bool audioDecodeOK() { return m_basebandSink->audioDecodeOK(); }
bool videoActive() { return m_basebandSink->videoActive(); }
bool videoDecodeOK() { return m_basebandSink->videoDecodeOK(); }
+ bool udpRunning() { return m_basebandSink->udpRunning(); }
bool playVideo() { return m_basebandSink->playVideo(); }
diff --git a/plugins/channelrx/demoddatv/datvdemodbaseband.h b/plugins/channelrx/demoddatv/datvdemodbaseband.h
index bfcd9d0ee..522e36146 100644
--- a/plugins/channelrx/demoddatv/datvdemodbaseband.h
+++ b/plugins/channelrx/demoddatv/datvdemodbaseband.h
@@ -74,10 +74,12 @@ public:
void setBasebandSampleRate(int sampleRate); //!< To be used when supporting thread is stopped
void SetVideoRender(DATVideoRender *objScreen) { m_sink.SetVideoRender(objScreen); }
DATVideostream *getVideoStream() { return m_sink.getVideoStream(); }
+ DATVUDPStream *getUDPStream() { return m_sink.getUDPStream(); }
bool audioActive() { return m_sink.audioActive(); }
bool audioDecodeOK() { return m_sink.audioDecodeOK(); }
bool videoActive() { return m_sink.videoActive(); }
bool videoDecodeOK() { return m_sink.videoDecodeOK(); }
+ bool udpRunning() { return m_sink.udpRunning(); }
bool playVideo() { return m_sink.playVideo(); }
int getModcodModulation() const { return m_sink.getModcodModulation(); }
diff --git a/plugins/channelrx/demoddatv/datvdemodgui.cpp b/plugins/channelrx/demoddatv/datvdemodgui.cpp
index 31f5b828d..dc4255097 100644
--- a/plugins/channelrx/demoddatv/datvdemodgui.cpp
+++ b/plugins/channelrx/demoddatv/datvdemodgui.cpp
@@ -219,7 +219,12 @@ DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Ba
m_objDATVDemod->setCNRMeter(ui->cnrMeter);
m_objDATVDemod->SetVideoRender(ui->screenTV_2);
- connect(m_objDATVDemod->getVideoStream(), &DATVideostream::fifoData, this, &DATVDemodGUI::on_StreamDataAvailable);
+ if (m_settings.m_playerEnable) {
+ connect(m_objDATVDemod->getVideoStream(), &DATVideostream::fifoData, this, &DATVDemodGUI::on_StreamDataAvailable);
+ } else {
+ connect(m_objDATVDemod->getUDPStream(), &DATVUDPStream::fifoData, this, &DATVDemodGUI::on_StreamDataAvailable);
+ }
+
connect(ui->screenTV_2, &DATVideoRender::onMetaDataChanged, this, &DATVDemodGUI::on_StreamMetaDataChanged);
m_intPreviousDecodedData=0;
@@ -270,6 +275,7 @@ DATVDemodGUI::DATVDemodGUI(PluginAPI* objPluginAPI, DeviceUISet *deviceUISet, Ba
#endif
ui->playerIndicator->setStyleSheet("QLabel { background-color: gray; border-radius: 8px; }");
+ ui->udpIndicator->setStyleSheet("QLabel { background-color: gray; border-radius: 8px; }");
resetToDefaults(); // does applySettings()
}
@@ -376,6 +382,18 @@ void DATVDemodGUI::displaySettings()
ui->udpTS->setChecked(m_settings.m_udpTS);
ui->udpTSAddress->setText(m_settings.m_udpTSAddress);
ui->udpTSPort->setText(tr("%1").arg(m_settings.m_udpTSPort));
+ ui->playerEnable->setChecked(m_settings.m_playerEnable);
+
+ if (m_settings.m_playerEnable)
+ {
+ disconnect(m_objDATVDemod->getUDPStream(), &DATVUDPStream::fifoData, this, &DATVDemodGUI::on_StreamDataAvailable);
+ connect(m_objDATVDemod->getVideoStream(), &DATVideostream::fifoData, this, &DATVDemodGUI::on_StreamDataAvailable);
+ }
+ else
+ {
+ disconnect(m_objDATVDemod->getVideoStream(), &DATVideostream::fifoData, this, &DATVDemodGUI::on_StreamDataAvailable);
+ connect(m_objDATVDemod->getUDPStream(), &DATVUDPStream::fifoData, this, &DATVDemodGUI::on_StreamDataAvailable);
+ }
blockApplySettings(false);
}
@@ -565,6 +583,12 @@ void DATVDemodGUI::tick()
ui->playerIndicator->setStyleSheet("QLabel { background-color: gray; border-radius: 8px; }");
}
+ if (m_objDATVDemod->udpRunning()) {
+ ui->udpIndicator->setStyleSheet("QLabel { background-color: rgb(85, 232, 85); border-radius: 8px; }"); // green
+ } else {
+ ui->udpIndicator->setStyleSheet("QLabel { background-color: gray; border-radius: 8px; }");
+ }
+
return;
}
@@ -817,6 +841,24 @@ void DATVDemodGUI::on_StreamMetaDataChanged(DataTSMetaData2 *objMetaData)
}
}
+void DATVDemodGUI::on_playerEnable_clicked()
+{
+ m_settings.m_playerEnable = ui->playerEnable->isChecked();
+
+ if (m_settings.m_playerEnable)
+ {
+ disconnect(m_objDATVDemod->getUDPStream(), &DATVUDPStream::fifoData, this, &DATVDemodGUI::on_StreamDataAvailable);
+ connect(m_objDATVDemod->getVideoStream(), &DATVideostream::fifoData, this, &DATVDemodGUI::on_StreamDataAvailable);
+ }
+ else
+ {
+ disconnect(m_objDATVDemod->getVideoStream(), &DATVideostream::fifoData, this, &DATVDemodGUI::on_StreamDataAvailable);
+ connect(m_objDATVDemod->getUDPStream(), &DATVUDPStream::fifoData, this, &DATVDemodGUI::on_StreamDataAvailable);
+ }
+
+ applySettings();
+}
+
void DATVDemodGUI::displayRRCParameters(bool blnVisible)
{
ui->spiRollOff->setVisible(blnVisible);
diff --git a/plugins/channelrx/demoddatv/datvdemodgui.h b/plugins/channelrx/demoddatv/datvdemodgui.h
index 0532f36ca..4b16d02ca 100644
--- a/plugins/channelrx/demoddatv/datvdemodgui.h
+++ b/plugins/channelrx/demoddatv/datvdemodgui.h
@@ -92,6 +92,7 @@ private slots:
void on_udpTS_clicked(bool checked);
void on_udpTSAddress_editingFinished();
void on_udpTSPort_editingFinished();
+ void on_playerEnable_clicked();
private:
Ui::DATVDemodGUI* ui;
diff --git a/plugins/channelrx/demoddatv/datvdemodgui.ui b/plugins/channelrx/demoddatv/datvdemodgui.ui
index 0706b5807..b3fe41001 100644
--- a/plugins/channelrx/demoddatv/datvdemodgui.ui
+++ b/plugins/channelrx/demoddatv/datvdemodgui.ui
@@ -785,7 +785,7 @@
- 10
+ 70
250
16
16
@@ -813,6 +813,25 @@
+
+
+
+ 0
+ 250
+ 61
+ 16
+
+
+
+ Video player enable
+
+
+ Qt::RightToLeft
+
+
+ Video
+
+
@@ -892,6 +911,31 @@
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 16
+ 16
+
+
+
+ UDP thread running indicator
+
+
+ QLabel { background-color: gray; border-radius: 8px; }
+
+
+
+
+
+
-
diff --git a/plugins/channelrx/demoddatv/datvdemodsettings.cpp b/plugins/channelrx/demoddatv/datvdemodsettings.cpp
index 84becb337..e48e3ed71 100644
--- a/plugins/channelrx/demoddatv/datvdemodsettings.cpp
+++ b/plugins/channelrx/demoddatv/datvdemodsettings.cpp
@@ -62,6 +62,7 @@ void DATVDemodSettings::resetToDefaults()
m_udpTSAddress = "127.0.0.1";
m_udpTSPort = 8882;
m_udpTS = false;
+ m_playerEnable = true;
m_streamIndex = 0;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
@@ -111,6 +112,7 @@ QByteArray DATVDemodSettings::serialize() const
s.writeS32(33, m_maxBitflips);
s.writeString(34, m_softLDPCToolPath);
s.writeS32(35, m_softLDPCMaxTrials);
+ s.writeBool(36, m_playerEnable);
return s.final();
}
@@ -198,6 +200,7 @@ bool DATVDemodSettings::deserialize(const QByteArray& data)
d.readString(34, &m_softLDPCToolPath, "/opt/install/sdrangel/bin/ldpctool");
d.readS32(35, &tmp, 8);
m_softLDPCMaxTrials = tmp < 1 ? 1 : tmp > m_softLDPCMaxMaxTrials ? m_softLDPCMaxMaxTrials : tmp;
+ d.readBool(36, &m_playerEnable, true);
validateSystemConfiguration();
@@ -235,7 +238,11 @@ void DATVDemodSettings::debug(const QString& msg) const
<< " m_audioMute: " << m_audioMute
<< " m_audioDeviceName: " << m_audioDeviceName
<< " m_audioVolume: " << m_audioVolume
- << " m_videoMute: " << m_videoMute;
+ << " m_videoMute: " << m_videoMute
+ << " m_udpTS: " << m_udpTS
+ << " m_udpTSAddress: " << m_udpTSAddress
+ << " m_udpTSPort: " << m_udpTSPort
+ << " m_playerEnable: " << m_playerEnable;
}
bool DATVDemodSettings::isDifferent(const DATVDemodSettings& other)
@@ -255,7 +262,8 @@ bool DATVDemodSettings::isDifferent(const DATVDemodSettings& other)
|| (m_notchFilters != other.m_notchFilters)
|| (m_symbolRate != other.m_symbolRate)
|| (m_excursion != other.m_excursion)
- || (m_standard != other.m_standard));
+ || (m_standard != other.m_standard)
+ || (m_playerEnable != other.m_playerEnable));
}
void DATVDemodSettings::validateSystemConfiguration()
diff --git a/plugins/channelrx/demoddatv/datvdemodsettings.h b/plugins/channelrx/demoddatv/datvdemodsettings.h
index 009c4fcc6..27dec5065 100644
--- a/plugins/channelrx/demoddatv/datvdemodsettings.h
+++ b/plugins/channelrx/demoddatv/datvdemodsettings.h
@@ -100,6 +100,7 @@ struct DATVDemodSettings
QString m_udpTSAddress;
quint32 m_udpTSPort;
bool m_udpTS;
+ bool m_playerEnable;
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
bool m_useReverseAPI;
QString m_reverseAPIAddress;
diff --git a/plugins/channelrx/demoddatv/datvdemodsink.cpp b/plugins/channelrx/demoddatv/datvdemodsink.cpp
index f8bb361a8..931c70d87 100644
--- a/plugins/channelrx/demoddatv/datvdemodsink.cpp
+++ b/plugins/channelrx/demoddatv/datvdemodsink.cpp
@@ -155,6 +155,18 @@ bool DATVDemodSink::videoDecodeOK()
}
}
+bool DATVDemodSink::udpRunning()
+{
+ if (!r_videoplayer) {
+ return false;
+ }
+
+ bool udpRunning = r_videoplayer->isUDPRunning();
+ r_videoplayer->resetUDPRunning();
+
+ return udpRunning;
+}
+
bool DATVDemodSink::playVideo()
{
QMutexLocker mlock(&m_mutex);
@@ -565,6 +577,7 @@ void DATVDemodSink::InitDATVFramework()
m_objCfg.rrc_steps = 0; //auto
m_objVideoStream->resetTotalReceived();
+ m_udpStream.resetTotalReceived();
switch(m_settings.m_modulation)
{
@@ -861,7 +874,11 @@ void DATVDemodSink::InitDATVFramework()
r_derand = new leansdr::derandomizer(m_objScheduler, *p_rtspackets, *p_tspackets);
// OUTPUT
- r_videoplayer = new leansdr::datvvideoplayer(m_objScheduler, *p_tspackets, m_objVideoStream, &m_udpStream);
+ if (m_settings.m_playerEnable) {
+ r_videoplayer = new leansdr::datvvideoplayer(m_objScheduler, *p_tspackets, m_objVideoStream, &m_udpStream);
+ } else {
+ r_videoplayer = new leansdr::datvvideoplayer(m_objScheduler, *p_tspackets, nullptr, &m_udpStream);
+ }
m_blnDVBInitialized = true;
}
@@ -904,6 +921,7 @@ void DATVDemodSink::InitDATVS2Framework()
m_objCfg.rrc_steps = 0; //auto
m_objVideoStream->resetTotalReceived();
+ m_udpStream.resetTotalReceived();
switch(m_settings.m_modulation)
{
@@ -1224,7 +1242,11 @@ void DATVDemodSink::InitDATVS2Framework()
*/
// OUTPUT
- r_videoplayer = new leansdr::datvvideoplayer(m_objScheduler, *p_tspackets, m_objVideoStream, &m_udpStream);
+ if (m_settings.m_playerEnable) {
+ r_videoplayer = new leansdr::datvvideoplayer(m_objScheduler, *p_tspackets, m_objVideoStream, &m_udpStream);
+ } else {
+ r_videoplayer = new leansdr::datvvideoplayer(m_objScheduler, *p_tspackets, nullptr, &m_udpStream);
+ }
m_blnDVBInitialized = true;
}
diff --git a/plugins/channelrx/demoddatv/datvdemodsink.h b/plugins/channelrx/demoddatv/datvdemodsink.h
index 103013909..331efb3e1 100644
--- a/plugins/channelrx/demoddatv/datvdemodsink.h
+++ b/plugins/channelrx/demoddatv/datvdemodsink.h
@@ -67,10 +67,12 @@ public:
void setCNRMeter(LevelMeterSignalDB *cnrMeter);
void SetVideoRender(DATVideoRender *objScreen);
DATVideostream *getVideoStream() { return m_objVideoStream; }
+ DATVUDPStream *getUDPStream() { return &m_udpStream; }
bool audioActive();
bool audioDecodeOK();
bool videoActive();
bool videoDecodeOK();
+ bool udpRunning();
bool playVideo();
void stopVideo();
diff --git a/plugins/channelrx/demoddatv/datvudpstream.cpp b/plugins/channelrx/demoddatv/datvudpstream.cpp
index 84c4c98cd..3d997ff7c 100644
--- a/plugins/channelrx/demoddatv/datvudpstream.cpp
+++ b/plugins/channelrx/demoddatv/datvudpstream.cpp
@@ -26,7 +26,11 @@ DATVUDPStream::DATVUDPStream(int tsBlockSize) :
m_address(QHostAddress::LocalHost),
m_port(8882),
m_tsBlockSize(tsBlockSize),
- m_tsBlockIndex(0)
+ m_tsBlockIndex(0),
+ m_dataBytes(0),
+ m_percentBuffer(0),
+ m_totalBytes(0),
+ m_fifoSignalCount(0)
{
m_tsBuffer = new char[m_tsBlocksPerFrame*m_tsBlockSize];
}
@@ -45,15 +49,31 @@ void DATVUDPStream::pushData(const char *chrData, int nbTSBlocks)
for (int i = 0; i < nbTSBlocks; i++)
{
std::copy(chrData + i*m_tsBlockSize, chrData + (i+1)*m_tsBlockSize, m_tsBuffer + m_tsBlockIndex*m_tsBlockSize);
-
- if (m_tsBlockIndex < m_tsBlocksPerFrame - 1)
+
+ if (m_tsBlockIndex < m_tsBlocksPerFrame - 1)
{
m_tsBlockIndex++;
}
else
{
m_udpSocket.writeDatagram(m_tsBuffer, m_tsBlocksPerFrame*m_tsBlockSize, m_address, m_port);
+ m_dataBytes += m_tsBlocksPerFrame*m_tsBlockSize;
+ m_totalBytes += m_tsBlocksPerFrame*m_tsBlockSize;
+
+ if (++m_fifoSignalCount == 10)
+ {
+ emit fifoData(&m_dataBytes, &m_percentBuffer, &m_totalBytes);
+ m_fifoSignalCount = 0;
+ }
+
+ m_dataBytes = 0;
m_tsBlockIndex = 0;
}
}
}
+
+void DATVUDPStream::resetTotalReceived()
+{
+ m_totalBytes = 0;
+ emit fifoData(&m_dataBytes, &m_percentBuffer, &m_totalBytes);
+}
diff --git a/plugins/channelrx/demoddatv/datvudpstream.h b/plugins/channelrx/demoddatv/datvudpstream.h
index 5270f7a84..ec164df1a 100644
--- a/plugins/channelrx/demoddatv/datvudpstream.h
+++ b/plugins/channelrx/demoddatv/datvudpstream.h
@@ -22,22 +22,27 @@
#include
#include
#include
+#include
-class QString;
-
-class DATVUDPStream
+class DATVUDPStream : public QObject
{
+ Q_OBJECT
public:
DATVUDPStream(int tsBlockSize);
~DATVUDPStream();
void pushData(const char *chrData, int nbTSBlocks);
+ void resetTotalReceived();
void setActive(bool active) { m_active = active; }
+ bool isActive() const { return m_active; }
bool setAddress(const QString& address) { return m_address.setAddress(address); }
void setPort(quint16 port) { m_port = port; }
static const int m_tsBlocksPerFrame;
+signals:
+ void fifoData(int *dataBytes, int *percentBuffer, qint64 *totalReceived);
+
private:
bool m_active;
QUdpSocket m_udpSocket;
@@ -46,6 +51,10 @@ private:
int m_tsBlockSize;
int m_tsBlockIndex;
char *m_tsBuffer;
+ int m_dataBytes;
+ int m_percentBuffer;
+ qint64 m_totalBytes;
+ int m_fifoSignalCount;
};
-#endif // DATVUDPSTREAM_H
\ No newline at end of file
+#endif // DATVUDPSTREAM_H
diff --git a/plugins/channelrx/demoddatv/datvvideoplayer.h b/plugins/channelrx/demoddatv/datvvideoplayer.h
index 48c259ee1..dc5f54b1e 100644
--- a/plugins/channelrx/demoddatv/datvvideoplayer.h
+++ b/plugins/channelrx/demoddatv/datvvideoplayer.h
@@ -31,12 +31,13 @@ template struct datvvideoplayer: runnable
datvvideoplayer(
scheduler *sch,
pipebuf &_in,
- DATVideostream *objVideoStream,
+ DATVideostream *videoStream,
DATVUDPStream *udpStream) :
runnable(sch, _in.name),
in(_in),
- m_objVideoStream(objVideoStream),
- m_udpStream(udpStream)
+ m_videoStream(videoStream),
+ m_udpStream(udpStream),
+ m_atomicUDPRunning(0)
{
}
@@ -48,38 +49,53 @@ template struct datvvideoplayer: runnable
return;
}
+ int nw;
+
m_udpStream->pushData((const char *) in.rd(), in.readable());
- int nw = m_objVideoStream->pushData((const char *) in.rd(), size);
+ m_atomicUDPRunning.storeRelaxed(m_udpStream->isActive() && (size > 0) ? 1 : 0);
- if (!nw)
+ if (m_videoStream)
{
- fatal("leansdr::datvvideoplayer::run: pipe");
- return;
- }
+ nw = m_videoStream->pushData((const char *) in.rd(), size);
- if (nw < 0)
+ if (!nw)
+ {
+ fatal("leansdr::datvvideoplayer::run: pipe");
+ return;
+ }
+
+ if (nw < 0)
+ {
+ fatal("leansdr::datvvideoplayer::run: write");
+ return;
+ }
+
+ if (nw % sizeof(T))
+ {
+ fatal("leansdr::datvvideoplayer::run: partial write");
+ return;
+ }
+
+ if (nw != size) {
+ fprintf(stderr, "leansdr::datvvideoplayer::run: nw: %d size: %d\n", nw, size);
+ }
+ }
+ else
{
- fatal("leansdr::datvvideoplayer::run: write");
- return;
- }
-
- if (nw % sizeof(T))
- {
- fatal("leansdr::datvvideoplayer::run: partial write");
- return;
- }
-
- if (nw != size) {
- fprintf(stderr, "leansdr::datvvideoplayer::run: nw: %d size: %d\n", nw, size);
+ nw = size;
}
in.read(nw / sizeof(T));
}
+ bool isUDPRunning() const { return m_atomicUDPRunning.loadRelaxed() == 1; }
+ void resetUDPRunning() { m_atomicUDPRunning.storeRelaxed(0); }
+
private:
pipereader in;
- DATVideostream *m_objVideoStream;
+ DATVideostream *m_videoStream;
DATVUDPStream *m_udpStream;
+ QAtomicInt m_atomicUDPRunning;
};
}
diff --git a/plugins/channelrx/demoddatv/readme.md b/plugins/channelrx/demoddatv/readme.md
index b13fca276..d6ab075b5 100644
--- a/plugins/channelrx/demoddatv/readme.md
+++ b/plugins/channelrx/demoddatv/readme.md
@@ -44,7 +44,7 @@ Power of signal received in the channel (dB)
This gauge gives the MER estimation. The averaged value appears on the right.
-
B.3: CNR estimation
+B.4: CNR estimation
This gauge gives the CNR estimation. The averaged value appears on the right.
@@ -60,6 +60,16 @@ This is the address of the TS UDP
This is the port of the TS UDP
+B.8: UDP streaming indicator
+
+Indicator turns green if UDP data is streaming to destination
+
+B.9: Video player enable and indicator
+
+Use the checkbox to enable or disable the internal video player. The indicator next turns green if the video player thread is active.
+
+Use this control to disable the video player if it causes too many crashes...
+
B.1: Symbol constellation
This is the constellation of the PSK or QAM synchronized signal. When the demodulation parameters are set correctly (modulation type, symbol rate and filtering) and signal is strong enough to recover symbol synchronization the purple dots appear close to the white crosses. White crosses represent the ideal symbols positions in the I/Q plane.
@@ -143,13 +153,15 @@ Push this button when you are lost...
B.2a.12: Amount of data decoded
-Automatically adjusts unit (kB, MB, ...)
+Amount data received in the video player. If the video player is disabled this is the amount of data sent via UDP. Automatically adjusts unit (kB, MB, ...)
B.2a.13: Stream speed
+Current data flow in the video player. If the video player is disabled this is the flow sent via UDP.
+
B.2a.14: Buffer status
-Gauge that shows percentage of buffer queue length
+Gauge that shows percentage of video player buffer queue length
B.2b: DATV signal settings (DVB-S2 specific)