From b1ba80b84720533d07b933a7945fc607bf75c9bb Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 3 Dec 2022 13:33:38 +0100 Subject: [PATCH] Local Sink: auto detect Local Input devices and fix crashes in most situations. Part of #871 --- doc/img/LocalSink.png | Bin 13272 -> 13417 bytes doc/img/LocalSink.xcf | Bin 41919 -> 42950 bytes plugins/channelrx/localsink/localsink.cpp | 133 ++++++++++++++---- plugins/channelrx/localsink/localsink.h | 31 +++- .../channelrx/localsink/localsinkbaseband.cpp | 17 +-- .../channelrx/localsink/localsinkbaseband.h | 20 --- plugins/channelrx/localsink/localsinkgui.cpp | 58 ++++---- plugins/channelrx/localsink/localsinkgui.h | 5 +- plugins/channelrx/localsink/localsinkgui.ui | 32 +---- .../channelrx/localsink/localsinksettings.cpp | 6 +- .../channelrx/localsink/localsinksettings.h | 2 +- plugins/channelrx/localsink/localsinksink.cpp | 76 +++++----- plugins/channelrx/localsink/localsinksink.h | 1 + plugins/channelrx/localsink/readme.md | 6 +- 14 files changed, 218 insertions(+), 169 deletions(-) diff --git a/doc/img/LocalSink.png b/doc/img/LocalSink.png index 7fc53133baffe265ddfc5a35cca664366d536b10..dec1912ac61387a5f8b82226380d093228c7cdc3 100644 GIT binary patch literal 13417 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 literal 13272 zcmb7r1zc6%wVRDB&1)U&!JwwMT9m^v2TKc z@&H9b?76(d=*EPzOYp&5^Ul!b1j4>BoL#>w-TR%?t%Wzg%-bIBGUL2`b)eAt9kk71d8ITpXOYY<|AJ9ujb= zj`}~`^v@;aP5*rFpPT;q9`dGtzK3k-Ki~AQ*x%Ld zwL{#b|HYzycJgfeOUvBkA_|HpCOXr5o0fOf-Uk=89$M=)UCSS>_c%T~+k3u*FO%+A zWS;(+HeHs;jqV-}y-p!HnQ!)+&-@!w^4#iL^7#mvg~*q-u0_hVO|SO)=&pSfG$wRo zR(&Jz_}A}lF>i0H#j>g4ol)b_z8CXRtA%cYGHz}|PxWKE<7dvyCVmm!Z%>@cVy_a9 zQqMt0V5AXVW|YTtmcD7L+LM_~;Ms~zay}d}FyykPt-VW`YwqnkUihYRE>!&1?b|aG z&pk27^6a-JZCv)>DkGw{5VrL+oO@eqD;jo1EbE7MmbHY;%EbEo}u^XT4E|gk0?Y7ck-bsyQREo;b ziSo$!qF~zREmV8aWQ~s9>pmsq!6I<6q>_v#>vAQ?+SI_dz5QWxeQJuE>Ep3QqJWJr z5!Is=gfoxACSohLZ`XV>-oWiDVF`m!VGv z^?%qZ%l|r!^VWRq_nlwFw+D6`uY}zblh=lB@TK<$XRC-Nu&7()D2Ha6 zj&3(sIck+y%`CrN9LjlpUgz1cKG~VhWgJFEe)sndrF%oV*@UqSD<6Y@r8;7E>L+Sc zHWN1%X~Rf<{`jMdi>i7}_pKJ_=4~7t2gNM?`>9rYi#E|*0SfAsUmbQ+xKmz{+)l@% zlX;W;@@_Dtu-n*|Fpmu1@$>Un)jvx^2`NM3k|=X)YEX35%D);%(=cMf5F7=Ow{zBB z$s|p=CGRaIEjedyNIn$x!7VZ-B46s%`DpWG!=U4L&j{a?B_}T4Rcq$D2^rayBkp-m zV}KEnJRch zl$5G9C$y0s9?F-~o=m0#{$C%8VsHsszrz)vmC?2AOLH0ci8?S=lDwl}_K3MQ-k{a#*G-S%cDT`AH*cRL!t z@vJvPE+w8%u3XV9>}`^9_r z%opd>&bNhmXL+>brqJm!*H+EiKo(Jgfc@h1>B0GS1KxU`T8vk{tejqD?Pwe0hF#Sm zX^wu^eCH3*ypc`$j>8QM8k~yMnytaRgSn|13vEp0Z3ojWf8V@+Ji>@@3zjgQ@_QA7 zNmo#-<$tiPRa=_nJ5H-D_!M6%b;A3zPjR|Gz^7!$V*A#BB0=a_Tka%+KuYg??AVJo@J(^tymfp)B&9&+jYIr#G8|$SDKI}S8U&F%Q{jv-X4=3T& z58SZz!XSud`*3~A{YvoR!-por`JZ{FT<~~!%0wcM8uZF6xlM+@qTMA$nO<6I&Q(XS z#Ym0riHVt&Y4JPlKZYkN=P0?m4I7o<5)wA`B#WDFNk_ANo}brsI$W1%4klljsukuk z>erfY2_1^h4GF>aWHIjE+-asPLzPogBMW)Dduy>4;ZB4TTD5-4mu5fZ`ptH2a7OJ} zz&MJ2Q4IW>XUG|@=&kgorlh8hT-WRW^_KT?Lc6TzqwezGAH~K)Sjj!F92|J@Xr&$q z3MMoL5Y5fa)we}5o-BORblC3^Hyp|(FEJX>ZVjVBz8<&f=-(fr=oIefmRJ-|UtOFY z+`e^7Yamm><>Fun<$O)^N_+bEXXPBlQq!@Q6P32Nv9J~qTQze9%@oPzQ#HK&{Msd| zC#P$9D%DOUhx#~ggEEQ*+%KQr=Qer3$;ml26B84Ie9j&Ovy(72^_Q|qr-1Y%w^*lx z)emgMuU@@+AS6WbNyzo|9zMQUt=k1e6XV;rZ_lT$E-2xP;d37VrD7jm8#dDLW>U5pV9rvunf8r7rKS7 zZa(1Pz!eu4XORE(?9H2d>9R@sSC{ePUPC$W4dN6P6$zKRlhU1!HeupChliClTn`jD zI5>)V0(?C(vZHy-DOOfiDkfjc${I{oIi3WYm#D&!>+di3JU!p8uiE~D>EPhN=6q;^ z@>EdJai@_;rOb?cZ7^r9PgaB_*NDW1YdA_u?TGg2i+Jz7uSdSeO+SGa{!4Yv0zY*Vj!5S=6Ep)HF3UW4Vl* znu6||ZoLo_+g?de+C3Q3oLgOOgVt249N2{~_T>`0&(6-Kw$Ia2Ql>3ma6TenSzDdm zU+Js5x;%TJU zm-_m&EG#V1QnVfVoDeh)PELe|8b=#r;+?Tv8A^E+;=yDyu*%ZMr(8J(a@FG?6R=y& zzhcp-dIe-Vadi3s1M}#?ld9v<35?7nvA$D@(9Xf>;uda>oFf&DiPF}@ z>f`+E(F$uiSP||SNwVRs4c>R}BU2#^cDh73t$eOpjHr*9boga z^?Xwm{LlS^V|GYtY%4)%Ti?KxA~~LEZd*shI^~7HJP29 ze>~K{AxHJYZ^}_xDxR6MEG>pqZh?|H^d-Vk@qm^`4EQaERE7}EUZ;UK6ukv)qWcQE zn3NN|`53pBROQ^7!j{>gyw=dQV-*mM+JDb1+qxpx=j#Wd!);XQxmpW}?H)A4{ynjG&-$obo7x4#u{EE1)S9Dt3g4Mm4iu3PYja^U^)8tU z$g47&%fprWadSPD#LGM`uN9etxxF7jk-JoZ3QZ>1-H`1(I@M*FB1<%${{& zZ%^2jNS&e)=sd2dtnAJIAo?C9g7(|>z3Dp77g$|r1K~03>$h4BgYU!=wp3ug5MD+f zkexkY>7l8KpGr9`hklw{u>^ZqB;5&mrtq`b2`?^6tf}$ji;BFm5q|dcJ}vEo_nHw@h05BU-~4j{dg

=%4tI4UlhTuY-WyhCk+{*fLN4ehjt_odLp9k$-Y zOk(%;_FCB5me|&6kkZi7&aSOFx65)7+8>dAGtyNm)zujPY*GC@6vvR-F#I*U4EqF& zr^Jv~fMQ?OYQ}M#!$^ahXRDFdFVB0GXnzKO|26;Jnku!)rkFLbS#$G|aE?;;0)hJx z=~qpn`;C;h%9P%WpKDc~40*L@{?vHHVk{}arZS!LQqN6KHD~Z0rPLvDyq-fyU48?;|pa$yKRCRIWfqnrt;xhoc7$ieG{_Ka6By&>{pn9!IC#k{T$= z*fufBJ-Smv*mk07ff(zka9n5!9#E2(d@~jDa4eg&uagiL*8_nNAPn#d47~F*Ir(Ix zY)ZNKV`xrjPZ&VdqtpEr&$zhzBO@c{$8#ZHzI=&jvyHSCu$O25rj+lzS>dCk;pj!m zW%Tvy*WZC8oWnJ)2-D;5&pnZOnl{`I>=aJRS+OwSR|0lSw{PEeIp1kky6QUEXcw}U z%H?1ks&RFOXNUsvC{SjCOj>9%Qm8i842hpe!{PJURxKs`ATucdPYl2#5sO;+3?6F6 z6=xXfqL_Xlj&bG8!k(HhWTR;d3@mw7Y-C#^U7CW!UxF z@UbX`?mW}ddH~5RMYY6;nwEAKU}10S>XHbBm6f$AnEW+7uC=3s8Ym86{*G0UQp}ENDR-UgXcAWR~q&eC;IxB-Y_aWzf2f9X0N5^T@{}{Z0765jCz0<4H zp;}@-D{3T0w?{GS>gxUlNNoxv@r0l-9?C_MCP00w!*vzpNXf+Wey*(*7WKi>tMfnw zjQ82!-(O4g>F9gO;u3zw7)`$RN;@CZEGXR4LUDsz7^%E1H|1ZvYyE3_!vFR6s}Lyy}-{hZ`{Cy1)}V;`unIG=-JWJ%ZoFB6G4c*@4&`7 zl7y3nhvTs@nnNg)%=suN{<*^WLG;rfLAr(6*+1@=dz3JSjiV*Trd#Of=y;Tr=3}L@ zLMPN+U0qd&qsF@W`qwRh^CBY>-onz#s`7_cTfJ6e&zQf#Sv*G{Rz|7(-y?^XuMd%s zPM74uF_aHfu*k5F77Rv|msg}53bm(p~<;L^~zdRnAdp#4Hs`tG84BE~u=rp)=Z_e9S+dyhWtnN}>Nr=;8? zBa;GdqYU!_OY9K?gTF?#(+7^2XhCQ0YNrEju&M44vwd0}$l4p$3Hk7Z^aP^Kv_2P7 zw>J=M)iSeA7-)Y8TZK&df{k^aObDF*JWb)L4?ftGi=agqzOYEoHD4;mJ*EZtza(!kt#b4Qf`fyf81?(sd7}4~nQKD6Z3S*%P3|)3xI_+Nb-Z5G?e9X$PD8U0J47rf zC}`eV-y2Lp7u*NY&(RuJ0c@e;2aq8_((Lxj$<8b;dbdUh%)`2A0oaKac6Uqs zS|Oj+)zZ=;Cua@pW?-PCrCv=9!_8zS!G3b4nbN)EKq=$hWWV7^Aq_Ha0Nr#AG?(UX zvrzoK;#A(nRSTpq8QUopRC-flq%b=>yS?4w_}98Cp6@CfKRFacJW5q{wS<&Z@m<9J z<$%XZm}DdaJE%zr{f3!(ACLtf#>*|$$}K)Kzc0maw_Sglhy@1Q{*CHag%F3{)sF!wjc8MR%%*;~K(!ye6{fmmM6F(?9|8gBIHe4T#M5zCG z5kR=$M1Jqy@8g|05-ub8u8^MCp$d^J!Mjv$J_%D1s~Z|l9xI)uZ)&ACk621hY)r7L z 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 HEsy^Jl}>Zm diff --git a/doc/img/LocalSink.xcf b/doc/img/LocalSink.xcf index 533e62ecf0cf5eda5184014dacb0e94fe80a879a..a8e17bdfee4c9db4e1c431b8435b3c96b64dfddb 100644 GIT binary patch literal 42950 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 diff --git a/plugins/channelrx/localsink/localsink.cpp b/plugins/channelrx/localsink/localsink.cpp index 93bfeb64e..b0e7d3684 100644 --- a/plugins/channelrx/localsink/localsink.cpp +++ b/plugins/channelrx/localsink/localsink.cpp @@ -34,6 +34,7 @@ #include "dsp/hbfilterchainconverter.h" #include "dsp/devicesamplemimo.h" #include "device/deviceapi.h" +#include "device/deviceset.h" #include "feature/feature.h" #include "settings/serializable.h" #include "maincore.h" @@ -42,6 +43,7 @@ #include "localsink.h" MESSAGE_CLASS_DEFINITION(LocalSink::MsgConfigureLocalSink, Message) +MESSAGE_CLASS_DEFINITION(LocalSink::MsgReportDevices, Message) const char* const LocalSink::m_channelIdURI = "sdrangel.channel.localsink"; const char* const LocalSink::m_channelId = "LocalSink"; @@ -75,7 +77,19 @@ LocalSink::LocalSink(DeviceAPI *deviceAPI) : this, &LocalSink::handleIndexInDeviceSetChanged ); - start(); + // Update device list when devices are added or removed + QObject::connect( + MainCore::instance(), + &MainCore::deviceSetAdded, + this, + &LocalSink::updateDeviceSetList + ); + QObject::connect( + MainCore::instance(), + &MainCore::deviceSetRemoved, + this, + &LocalSink::updateDeviceSetList + ); } LocalSink::~LocalSink() @@ -89,7 +103,7 @@ LocalSink::~LocalSink() delete m_networkManager; m_deviceAPI->removeChannelSinkAPI(this); m_deviceAPI->removeChannelSink(this); - stop(); + stopProcessing(); } void LocalSink::setDeviceAPI(DeviceAPI *deviceAPI) @@ -119,12 +133,18 @@ void LocalSink::feed(const SampleVector::const_iterator& begin, const SampleVect } void LocalSink::start() +{ } + +void LocalSink::stop() +{ } + +void LocalSink::startProcessing() { if (m_running) { return; } - qDebug("LocalSink::start"); + qDebug("LocalSink::startProcessing"); m_thread = new QThread(this); m_basebandSink = new LocalSinkBaseband(); m_basebandSink->moveToThread(m_thread); @@ -135,19 +155,24 @@ void LocalSink::start() m_basebandSink->reset(); m_thread->start(); - LocalSinkBaseband::MsgConfigureLocalSinkBaseband *msg = LocalSinkBaseband::MsgConfigureLocalSinkBaseband::create(m_settings, true); - m_basebandSink->getInputMessageQueue()->push(msg); + DeviceSampleSource *deviceSource = getLocalDevice(m_settings.m_localDeviceIndex); + LocalSinkBaseband::MsgConfigureLocalDeviceSampleSource *msgDevice = + LocalSinkBaseband::MsgConfigureLocalDeviceSampleSource::create(deviceSource); + m_basebandSink->getInputMessageQueue()->push(msgDevice); + + LocalSinkBaseband::MsgConfigureLocalSinkBaseband *msgConfig = LocalSinkBaseband::MsgConfigureLocalSinkBaseband::create(m_settings, true); + m_basebandSink->getInputMessageQueue()->push(msgConfig); m_running = true; } -void LocalSink::stop() +void LocalSink::stopProcessing() { if (!m_running) { return; } - qDebug("LocalSink::stop"); + qDebug("LocalSink::stopProcessing"); m_running = false; m_thread->exit(); m_thread->wait(); @@ -218,27 +243,15 @@ bool LocalSink::deserialize(const QByteArray& data) } } -void LocalSink::getLocalDevices(std::vector& indexes) +DeviceSampleSource *LocalSink::getLocalDevice(int index) { - indexes.clear(); - DSPEngine *dspEngine = DSPEngine::instance(); - - for (uint32_t i = 0; i < dspEngine->getDeviceSourceEnginesNumber(); i++) - { - DSPDeviceSourceEngine *deviceSourceEngine = dspEngine->getDeviceSourceEngineByIndex(i); - DeviceSampleSource *deviceSource = deviceSourceEngine->getSource(); - - if (deviceSource->getDeviceDescription() == "LocalInput") { - indexes.push_back(i); - } + if (index < 0) { + return nullptr; } -} -DeviceSampleSource *LocalSink::getLocalDevice(uint32_t index) -{ DSPEngine *dspEngine = DSPEngine::instance(); - if (index < dspEngine->getDeviceSourceEnginesNumber()) + if (index < (int) dspEngine->getDeviceSourceEnginesNumber()) { DSPDeviceSourceEngine *deviceSourceEngine = dspEngine->getDeviceSourceEngineByIndex(index); DeviceSampleSource *deviceSource = deviceSourceEngine->getSource(); @@ -266,7 +279,7 @@ DeviceSampleSource *LocalSink::getLocalDevice(uint32_t index) return nullptr; } -void LocalSink::propagateSampleRateAndFrequency(uint32_t index, uint32_t log2Decim) +void LocalSink::propagateSampleRateAndFrequency(int index, uint32_t log2Decim) { qDebug() << "LocalSink::propagateSampleRateAndFrequency:" << " index: " << index @@ -329,10 +342,10 @@ void LocalSink::applySettings(const LocalSinkSettings& settings, bool force) { reverseAPIKeys.append("play"); - if (m_running) - { - LocalSinkBaseband::MsgConfigureLocalSinkWork *msg = LocalSinkBaseband::MsgConfigureLocalSinkWork::create(settings.m_play); - m_basebandSink->getInputMessageQueue()->push(msg); + if (settings.m_play) { + startProcessing(); + } else { + stopProcessing(); } } @@ -682,3 +695,67 @@ void LocalSink::handleIndexInDeviceSetChanged(int index) .arg(index); m_basebandSink->setFifoLabel(fifoLabel); } + +void LocalSink::updateDeviceSetList() +{ + MainCore *mainCore = MainCore::instance(); + std::vector& deviceSets = mainCore->getDeviceSets(); + std::vector::const_iterator it = deviceSets.begin(); + + m_localInputDeviceIndexes.clear(); + unsigned int deviceIndex = 0; + + for (; it != deviceSets.end(); ++it, deviceIndex++) + { + DSPDeviceSourceEngine *deviceSourceEngine = (*it)->m_deviceSourceEngine; + + if (deviceSourceEngine) + { + DeviceSampleSource *deviceSource = deviceSourceEngine->getSource(); + + if (deviceSource->getDeviceDescription() == "LocalInput") { + m_localInputDeviceIndexes.append(deviceIndex); + } + } + } + + if (m_guiMessageQueue) + { + MsgReportDevices *msg = MsgReportDevices::create(); + msg->getDeviceSetIndexes() = m_localInputDeviceIndexes; + m_guiMessageQueue->push(msg); + } + + LocalSinkSettings settings = m_settings; + int newIndexInList; + + if (it != deviceSets.begin()) + { + if (m_settings.m_localDeviceIndex < 0) { + newIndexInList = 0; + } else if (m_settings.m_localDeviceIndex >= m_localInputDeviceIndexes.size()) { + newIndexInList = m_localInputDeviceIndexes.size() - 1; + } else { + newIndexInList = m_settings.m_localDeviceIndex; + } + } + else + { + newIndexInList = -1; + } + + if (newIndexInList < 0) { + settings.m_localDeviceIndex = -1; // means no device + } else { + settings.m_localDeviceIndex = m_localInputDeviceIndexes[newIndexInList]; + } + + qDebug("LocalSink::updateDeviceSetLists: new device index: %d device: %d", newIndexInList, settings.m_localDeviceIndex); + applySettings(settings); + + if (m_guiMessageQueue) + { + MsgConfigureLocalSink *msg = MsgConfigureLocalSink::create(m_settings, false); + m_guiMessageQueue->push(msg); + } +} diff --git a/plugins/channelrx/localsink/localsink.h b/plugins/channelrx/localsink/localsink.h index cc21ed061..75bd5c27a 100644 --- a/plugins/channelrx/localsink/localsink.h +++ b/plugins/channelrx/localsink/localsink.h @@ -45,8 +45,7 @@ public: const LocalSinkSettings& getSettings() const { return m_settings; } bool getForce() const { return m_force; } - static MsgConfigureLocalSink* create(const LocalSinkSettings& settings, bool force) - { + static MsgConfigureLocalSink* create(const LocalSinkSettings& settings, bool force) { return new MsgConfigureLocalSink(settings, force); } @@ -61,6 +60,24 @@ public: { } }; + class MsgReportDevices : public Message { + MESSAGE_CLASS_DECLARATION + + public: + QList& getDeviceSetIndexes() { return m_deviceSetIndexes; } + + static MsgReportDevices* create() { + return new MsgReportDevices(); + } + + private: + QList m_deviceSetIndexes; + + MsgReportDevices() : + Message() + { } + }; + LocalSink(DeviceAPI *deviceAPI); virtual ~LocalSink(); virtual void destroy() { delete this; } @@ -116,8 +133,8 @@ public: const QStringList& channelSettingsKeys, SWGSDRangel::SWGChannelSettings& response); - void getLocalDevices(std::vector& indexes); uint32_t getNumberOfDeviceStreams() const; + const QList& getDeviceSetList() { return m_localInputDeviceIndexes; } static const char* const m_channelIdURI; static const char* const m_channelId; @@ -128,6 +145,7 @@ private: LocalSinkBaseband *m_basebandSink; bool m_running; LocalSinkSettings m_settings; + QList m_localInputDeviceIndexes; uint64_t m_centerFrequency; int64_t m_frequencyOffset; @@ -138,10 +156,13 @@ private: virtual bool handleMessage(const Message& cmd); void applySettings(const LocalSinkSettings& settings, bool force = false); - void propagateSampleRateAndFrequency(uint32_t index, uint32_t log2Decim); + void propagateSampleRateAndFrequency(int index, uint32_t log2Decim); static void validateFilterChainHash(LocalSinkSettings& settings); void calculateFrequencyOffset(uint32_t log2Decim, uint32_t filterChainHash); - DeviceSampleSource *getLocalDevice(uint32_t index); + void updateDeviceSetList(); + DeviceSampleSource *getLocalDevice(int index); + void startProcessing(); + void stopProcessing(); void webapiReverseSendSettings(QList& channelSettingsKeys, const LocalSinkSettings& settings, bool force); void sendChannelSettings( diff --git a/plugins/channelrx/localsink/localsinkbaseband.cpp b/plugins/channelrx/localsink/localsinkbaseband.cpp index e53cb31c8..10ccc2633 100644 --- a/plugins/channelrx/localsink/localsinkbaseband.cpp +++ b/plugins/channelrx/localsink/localsinkbaseband.cpp @@ -24,7 +24,6 @@ #include "localsinkbaseband.h" MESSAGE_CLASS_DEFINITION(LocalSinkBaseband::MsgConfigureLocalSinkBaseband, Message) -MESSAGE_CLASS_DEFINITION(LocalSinkBaseband::MsgConfigureLocalSinkWork, Message) MESSAGE_CLASS_DEFINITION(LocalSinkBaseband::MsgConfigureLocalDeviceSampleSource, Message) LocalSinkBaseband::LocalSinkBaseband() : @@ -43,10 +42,12 @@ LocalSinkBaseband::LocalSinkBaseband() : ); connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + m_sink.start(m_localSampleSource); } LocalSinkBaseband::~LocalSinkBaseband() { + m_sink.stop(); delete m_channelizer; } @@ -123,20 +124,6 @@ bool LocalSinkBaseband::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureLocalSinkWork::match(cmd)) - { - QMutexLocker mutexLocker(&m_mutex); - MsgConfigureLocalSinkWork& conf = (MsgConfigureLocalSinkWork&) cmd; - qDebug() << "LocalSinkBaseband::handleMessage: MsgConfigureLocalSinkWork: " << conf.isWorking(); - - if (conf.isWorking()) { - m_sink.start(m_localSampleSource); - } else { - m_sink.stop(); - } - - return true; - } else if (MsgConfigureLocalDeviceSampleSource::match(cmd)) { QMutexLocker mutexLocker(&m_mutex); diff --git a/plugins/channelrx/localsink/localsinkbaseband.h b/plugins/channelrx/localsink/localsinkbaseband.h index 00b5e1946..5a520ef81 100644 --- a/plugins/channelrx/localsink/localsinkbaseband.h +++ b/plugins/channelrx/localsink/localsinkbaseband.h @@ -57,26 +57,6 @@ public: { } }; - class MsgConfigureLocalSinkWork : public Message { - MESSAGE_CLASS_DECLARATION - - public: - bool isWorking() const { return m_working; } - - static MsgConfigureLocalSinkWork* create(bool working) - { - return new MsgConfigureLocalSinkWork(working); - } - - private: - bool m_working; - - MsgConfigureLocalSinkWork(bool working) : - Message(), - m_working(working) - { } - }; - class MsgConfigureLocalDeviceSampleSource : public Message { MESSAGE_CLASS_DECLARATION diff --git a/plugins/channelrx/localsink/localsinkgui.cpp b/plugins/channelrx/localsink/localsinkgui.cpp index f73bcce3d..162c49220 100644 --- a/plugins/channelrx/localsink/localsinkgui.cpp +++ b/plugins/channelrx/localsink/localsinkgui.cpp @@ -22,7 +22,6 @@ #include "gui/devicestreamselectiondialog.h" #include "dsp/hbfilterchainconverter.h" #include "dsp/dspcommands.h" -#include "mainwindow.h" #include "localsinkgui.h" #include "localsink.h" @@ -53,7 +52,6 @@ QByteArray LocalSinkGUI::serialize() const bool LocalSinkGUI::deserialize(const QByteArray& data) { - updateLocalDevices(); if (m_settings.deserialize(data)) { @@ -90,6 +88,12 @@ bool LocalSinkGUI::handleMessage(const Message& message) blockApplySettings(false); return true; } + else if (LocalSink::MsgReportDevices::match(message)) + { + LocalSink::MsgReportDevices& report = (LocalSink::MsgReportDevices&) message; + updateDeviceSetList(report.getDeviceSetIndexes()); + return true; + } else { return false; @@ -131,7 +135,7 @@ LocalSinkGUI::LocalSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); - updateLocalDevices(); + updateDeviceSetList(m_localSink->getDeviceSetList()); displaySettings(); makeUIConnections(); applySettings(true); @@ -199,18 +203,6 @@ void LocalSinkGUI::displayRateAndShift() m_channelMarker.setBandwidth(channelSampleRate); } -void LocalSinkGUI::updateLocalDevices() -{ - std::vector localDevicesIndexes; - m_localSink->getLocalDevices(localDevicesIndexes); - ui->localDevice->clear(); - std::vector::const_iterator it = localDevicesIndexes.begin(); - - for (; it != localDevicesIndexes.end(); ++it) { - ui->localDevice->addItem(tr("%1").arg(*it), QVariant(*it)); - } -} - int LocalSinkGUI::getLocalDeviceIndexInCombo(int localDeviceIndex) { int index = 0; @@ -224,7 +216,6 @@ int LocalSinkGUI::getLocalDeviceIndexInCombo(int localDeviceIndex) return -1; } - void LocalSinkGUI::leaveEvent(QEvent* event) { m_channelMarker.setHighlighted(false); @@ -243,8 +234,7 @@ void LocalSinkGUI::handleSourceMessages() while ((message = getInputMessageQueue()->pop()) != 0) { - if (handleMessage(*message)) - { + if (handleMessage(*message)) { delete message; } } @@ -306,6 +296,21 @@ void LocalSinkGUI::onMenuDialogCalled(const QPoint &p) resetContextMenuType(); } +void LocalSinkGUI::updateDeviceSetList(const QList& deviceSetIndexes) +{ + QList::const_iterator it = deviceSetIndexes.begin(); + + ui->localDevice->blockSignals(true); + + ui->localDevice->clear(); + + for (; it != deviceSetIndexes.end(); ++it) { + ui->localDevice->addItem(QString("R%1").arg(*it), *it); + } + + ui->localDevice->blockSignals(false); +} + void LocalSinkGUI::on_decimationFactor_currentIndexChanged(int index) { m_settings.m_log2Decim = index; @@ -320,18 +325,10 @@ void LocalSinkGUI::on_position_valueChanged(int value) void LocalSinkGUI::on_localDevice_currentIndexChanged(int index) { - m_settings.m_localDeviceIndex = ui->localDevice->itemData(index).toInt(); - applySettings(); -} - -void LocalSinkGUI::on_localDevicesRefresh_clicked(bool checked) -{ - (void) checked; - updateLocalDevices(); - int index = getLocalDeviceIndexInCombo(m_settings.m_localDeviceIndex); - - if (index >= 0) { - ui->localDevice->setCurrentIndex(index); + if (index >= 0) + { + m_settings.m_localDeviceIndex = ui->localDevice->currentData().toInt(); + applySettings(); } } @@ -379,7 +376,6 @@ void LocalSinkGUI::makeUIConnections() QObject::connect(ui->decimationFactor, QOverload::of(&QComboBox::currentIndexChanged), this, &LocalSinkGUI::on_decimationFactor_currentIndexChanged); QObject::connect(ui->position, &QSlider::valueChanged, this, &LocalSinkGUI::on_position_valueChanged); QObject::connect(ui->localDevice, QOverload::of(&QComboBox::currentIndexChanged), this, &LocalSinkGUI::on_localDevice_currentIndexChanged); - QObject::connect(ui->localDevicesRefresh, &QPushButton::clicked, this, &LocalSinkGUI::on_localDevicesRefresh_clicked); QObject::connect(ui->localDevicePlay, &ButtonSwitch::toggled, this, &LocalSinkGUI::on_localDevicePlay_toggled); } diff --git a/plugins/channelrx/localsink/localsinkgui.h b/plugins/channelrx/localsink/localsinkgui.h index 1904c4dca..a5947d416 100644 --- a/plugins/channelrx/localsink/localsinkgui.h +++ b/plugins/channelrx/localsink/localsinkgui.h @@ -84,11 +84,11 @@ private: void applySettings(bool force = false); void displaySettings(); void displayRateAndShift(); - void updateLocalDevices(); - int getLocalDeviceIndexInCombo(int localDeviceIndex); bool handleMessage(const Message& message); void makeUIConnections(); void updateAbsoluteCenterFrequency(); + void updateDeviceSetList(const QList& deviceSetIndexes); + int getLocalDeviceIndexInCombo(int localDeviceIndex); void leaveEvent(QEvent*); void enterEvent(EnterEventType*); @@ -101,7 +101,6 @@ private slots: void on_decimationFactor_currentIndexChanged(int index); void on_position_valueChanged(int value); void on_localDevice_currentIndexChanged(int index); - void on_localDevicesRefresh_clicked(bool checked); void on_localDevicePlay_toggled(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); diff --git a/plugins/channelrx/localsink/localsinkgui.ui b/plugins/channelrx/localsink/localsinkgui.ui index 114225121..a1ef5e211 100644 --- a/plugins/channelrx/localsink/localsinkgui.ui +++ b/plugins/channelrx/localsink/localsinkgui.ui @@ -281,30 +281,10 @@ - - - - - 24 - 16777215 - - - - Refresh indexes of available local devices - - - - - - - :/recycle.png:/recycle.png - - - - Start/Stop sink + Start/Stop processing @@ -335,17 +315,17 @@ + + ButtonSwitch + QToolButton +

gui/buttonswitch.h
+ RollupContents QWidget
gui/rollupcontents.h
1
- - ButtonSwitch - QToolButton -
gui/buttonswitch.h
-
diff --git a/plugins/channelrx/localsink/localsinksettings.cpp b/plugins/channelrx/localsink/localsinksettings.cpp index ecf2f9a48..7f84cf6b7 100644 --- a/plugins/channelrx/localsink/localsinksettings.cpp +++ b/plugins/channelrx/localsink/localsinksettings.cpp @@ -30,7 +30,7 @@ LocalSinkSettings::LocalSinkSettings() void LocalSinkSettings::resetToDefaults() { - m_localDeviceIndex = 0; + m_localDeviceIndex = -1; m_rgbColor = QColor(140, 4, 4).rgb(); m_title = "Local sink"; m_log2Decim = 0; @@ -51,7 +51,7 @@ void LocalSinkSettings::resetToDefaults() QByteArray LocalSinkSettings::serialize() const { SimpleSerializer s(1); - s.writeU32(1, m_localDeviceIndex); + s.writeS32(1, m_localDeviceIndex); if (m_channelMarker) { s.writeBlob(2, m_channelMarker->serialize()); @@ -95,7 +95,7 @@ bool LocalSinkSettings::deserialize(const QByteArray& data) QString strtmp; QByteArray bytetmp; - d.readU32(1, &m_localDeviceIndex, 0); + d.readS32(1, &m_localDeviceIndex, -1); if (m_channelMarker) { diff --git a/plugins/channelrx/localsink/localsinksettings.h b/plugins/channelrx/localsink/localsinksettings.h index 48a52beaa..93cd41785 100644 --- a/plugins/channelrx/localsink/localsinksettings.h +++ b/plugins/channelrx/localsink/localsinksettings.h @@ -25,7 +25,7 @@ class Serializable; struct LocalSinkSettings { - uint32_t m_localDeviceIndex; + int m_localDeviceIndex; quint32 m_rgbColor; QString m_title; uint32_t m_log2Decim; diff --git a/plugins/channelrx/localsink/localsinksink.cpp b/plugins/channelrx/localsink/localsinksink.cpp index d3487b3e1..777d25ae1 100644 --- a/plugins/channelrx/localsink/localsinksink.cpp +++ b/plugins/channelrx/localsink/localsinksink.cpp @@ -26,12 +26,13 @@ #include "localsinksink.h" LocalSinkSink::LocalSinkSink() : - m_sinkWorker(nullptr), - m_running(false), - m_centerFrequency(0), - m_frequencyOffset(0), - m_sampleRate(48000), - m_deviceSampleRate(48000) + m_deviceSource(nullptr), + m_sinkWorker(nullptr), + m_running(false), + m_centerFrequency(0), + m_frequencyOffset(0), + m_sampleRate(48000), + m_deviceSampleRate(48000) { m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(4000000)); applySettings(m_settings, true); @@ -43,34 +44,39 @@ LocalSinkSink::~LocalSinkSink() void LocalSinkSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) { - m_sampleFifo.write(begin, end); + if (m_running && m_deviceSource) { + m_deviceSource->getSampleFifo()->write(begin, end); + } + // m_sampleFifo.write(begin, end); } void LocalSinkSink::start(DeviceSampleSource *deviceSource) { - qDebug("LocalSinkSink::start"); + qDebug("LocalSinkSink::start: deviceSource: %p", deviceSource); if (m_running) { stop(); } - m_sinkWorker = new LocalSinkWorker(); - m_sinkWorker->moveToThread(&m_sinkWorkerThread); - m_sinkWorker->setSampleFifo(&m_sampleFifo); + m_deviceSource = deviceSource; + // TODO: We'll see later if a worker is really needed + // m_sinkWorker = new LocalSinkWorker(); + // m_sinkWorker->moveToThread(&m_sinkWorkerThread); + // m_sinkWorker->setSampleFifo(&m_sampleFifo); - if (deviceSource) { - m_sinkWorker->setDeviceSampleFifo(deviceSource->getSampleFifo()); - } + // if (deviceSource) { + // m_sinkWorker->setDeviceSampleFifo(deviceSource->getSampleFifo()); + // } - QObject::connect( - &m_sampleFifo, - &SampleSinkFifo::dataReady, - m_sinkWorker, - &LocalSinkWorker::handleData, - Qt::QueuedConnection - ); + // QObject::connect( + // &m_sampleFifo, + // &SampleSinkFifo::dataReady, + // m_sinkWorker, + // &LocalSinkWorker::handleData, + // Qt::QueuedConnection + // ); - startWorker(); + // startWorker(); m_running = true; } @@ -78,21 +84,23 @@ void LocalSinkSink::stop() { qDebug("LocalSinkSink::stop"); - QObject::disconnect( - &m_sampleFifo, - &SampleSinkFifo::dataReady, - m_sinkWorker, - &LocalSinkWorker::handleData - ); + // TODO: We'll see later if a worker is really needed + // QObject::disconnect( + // &m_sampleFifo, + // &SampleSinkFifo::dataReady, + // m_sinkWorker, + // &LocalSinkWorker::handleData + // ); - if (m_sinkWorker != 0) - { - stopWorker(); - m_sinkWorker->deleteLater(); - m_sinkWorker = nullptr; - } + // if (m_sinkWorker != 0) + // { + // stopWorker(); + // m_sinkWorker->deleteLater(); + // m_sinkWorker = nullptr; + // } m_running = false; + m_deviceSource = nullptr; } void LocalSinkSink::startWorker() diff --git a/plugins/channelrx/localsink/localsinksink.h b/plugins/channelrx/localsink/localsinksink.h index 06bea9c5d..2552f4dca 100644 --- a/plugins/channelrx/localsink/localsinksink.h +++ b/plugins/channelrx/localsink/localsinksink.h @@ -43,6 +43,7 @@ public: void setSampleRate(int sampleRate); private: + DeviceSampleSource *m_deviceSource; SampleSinkFifo m_sampleFifo; LocalSinkSettings m_settings; LocalSinkWorker *m_sinkWorker; diff --git a/plugins/channelrx/localsink/readme.md b/plugins/channelrx/localsink/readme.md index 498cd2d75..b01e60972 100644 --- a/plugins/channelrx/localsink/readme.md +++ b/plugins/channelrx/localsink/readme.md @@ -12,7 +12,7 @@ These Local Sinks can then be coupled with two Local Input device source plugins Note that because it uses only the channelizer half band filter chain to achieve decimation and center frequency shift you have a limited choice on the center frequencies that may be used (similarly to the Remote Sink). The available center frequencies depend on the baseband sample rate, the channel decimation and the filter chain that is used so you have to play with these parameters to obtain a suitable center frequency and pass band. -⚠ Important warning When closing the application or before closing the local input device the local sink is connected to you have to stop the device where the local sink operates. This is because there is no reverse link for the local input to notify the local sink that it closes. Therefore closing the local input while the local sink runs will crash the program. +⚠ Important warning When closing the application or before closing the local input device the local sink is connected it is recommended to stop processing on the local sink (7). Depending on the sequence by which the devices have been created closing the local input while the local sink runs may crash the program.

Interface

@@ -52,6 +52,6 @@ The slider moves the channel center frequency roughly from the lower to the high This selects the index of the Local Input source where to send the I/Q samples. The list can be refreshed with the next button (7) -

7: Refresh local input devices list

+

7: Start/stop processing

-Use this button to refresh the list of Local Input sources indexes. +Use this button to start or stop processing.