diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp index 5142484e4..577c4a323 100644 --- a/plugins/samplesource/bladerf2input/bladerf2input.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -269,8 +269,8 @@ bool BladeRF2Input::start() // channel range allocated in the thread or past it. To perform the transition it stops the thread, deletes it and creates a new one. // It marks the thread as needing start. // - // If the requested channel is within the thread channel range (this thread being already allocated) it simply removes its FIFO reference - // so that the samples are not fed to the FIFO anymore and leaves the thread unchanged (no stop, no delete/new) + // If the requested channel is within the thread channel range (this thread being already allocated) it simply adds its FIFO reference + // so that the samples are fed to the FIFO and leaves the thread unchanged (no stop, no delete/new) // // If there is no thread allocated it creates a new one with a number of channels that fits the requested channel. That is // 1 if channel 0 is requested (SI mode) and 2 if channel 1 is requested (MI mode). It marks the thread as needing start. @@ -321,7 +321,7 @@ bool BladeRF2Input::start() bladerf2InputThread->setFcPos(i, fcPoss[i]); } - // remove old thread address from buddies (reset in all buddies) + // remove old thread address from buddies (reset in all buddies). The address being held only in the owning source. const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); std::vector::const_iterator it = sourceBuddies.begin(); @@ -457,7 +457,7 @@ void BladeRF2Input::stop() qDebug("BladeRF2Input::stop: do not re-create thread as there are no more FIFOs active"); } - // remove old thread address from buddies (reset in all buddies) + // remove old thread address from buddies (reset in all buddies). The address being held only in the owning source. const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); std::vector::const_iterator it = sourceBuddies.begin(); diff --git a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp index c5a1ad31a..059bf53a9 100644 --- a/plugins/samplesource/soapysdrinput/soapysdrinput.cpp +++ b/plugins/samplesource/soapysdrinput/soapysdrinput.cpp @@ -258,11 +258,233 @@ void SoapySDRInput::moveThreadToBuddy() bool SoapySDRInput::start() { - return false; + // There is a single thread per physical device (Rx side). This thread is unique and referenced by a unique + // buddy in the group of source buddies associated with this physical device. + // + // This start method is responsible for managing the thread and number of channels when the streaming of a Rx channel is started + // + // It checks the following conditions + // - the thread is allocated or not (by itself or one of its buddies). If it is it grabs the thread pointer. + // - the requested channel is the first (0) or the following + // + // There are two possible working modes: + // - Single Input (SI) with only one channel streaming. This HAS to be channel 0. + // - Multiple Input (MI) with two or more channels. It MUST be in this configuration if any channel other than 0 + // is used irrespective of what you actually do with samples coming from ignored channels. + // For example When we will run with only channel 2 streaming from the client perspective the channels 0 amnd 1 will actually + // be enabled and streaming but its samples will just be disregarded. + // This means that all channels up to the highest in index being used are activated. + // + // It manages the transition form SI where only one channel (the first or channel 0) should be running to the + // Multiple Input (MI) if the requested channel is 1 or more. More generally it checks if the requested channel is within the current + // channel range allocated in the thread or past it. To perform the transition it stops the thread, deletes it and creates a new one. + // It marks the thread as needing start. + // + // If the requested channel is within the thread channel range (this thread being already allocated) it simply adds its FIFO reference + // so that the samples are fed to the FIFO and leaves the thread unchanged (no stop, no delete/new) + // + // If there is no thread allocated it creates a new one with a number of channels that fits the requested channel. That is + // 1 if channel 0 is requested (SI mode) and 3 if channel 2 is requested (MI mode). It marks the thread as needing start. + // + // Eventually it registers the FIFO in the thread. If the thread has to be started it enables the channels up to the number of channels + // allocated in the thread and starts the thread. + // + // Note: this is quite similar to the BladeRF2 start handling. The main difference is that the channel allocation (enabling) process is + // done in the thread object. + + if (!m_deviceShared.m_device) + { + qDebug("SoapySDRInput::start: no device object"); + return false; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + SoapySDRInputThread *soapySDRInputThread = findThread(); + bool needsStart = false; + + if (soapySDRInputThread) // if thread is already allocated + { + qDebug("SoapySDRInput::start: thread is already allocated"); + + int nbOriginalChannels = soapySDRInputThread->getNbChannels(); + + if (requestedChannel+1 > nbOriginalChannels) // expansion by deleting and re-creating the thread + { + qDebug("SoapySDRInput::start: expand channels. Re-allocate thread and take ownership"); + + SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels]; + unsigned int *log2Decims = new unsigned int[nbOriginalChannels]; + int *fcPoss = new int[nbOriginalChannels]; + + for (int i = 0; i < nbOriginalChannels; i++) // save original FIFO references and data + { + fifos[i] = soapySDRInputThread->getFifo(i); + log2Decims[i] = soapySDRInputThread->getLog2Decimation(i); + fcPoss[i] = soapySDRInputThread->getFcPos(i); + } + + soapySDRInputThread->stopWork(); + delete soapySDRInputThread; + soapySDRInputThread = new SoapySDRInputThread(m_deviceShared.m_device, requestedChannel+1); + m_thread = soapySDRInputThread; // take ownership + + for (int i = 0; i < nbOriginalChannels; i++) // restore original FIFO references + { + soapySDRInputThread->setFifo(i, fifos[i]); + soapySDRInputThread->setLog2Decimation(i, log2Decims[i]); + soapySDRInputThread->setFcPos(i, fcPoss[i]); + } + + // remove old thread address from buddies (reset in all buddies). The address being held only in the owning source. + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) { + ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); + } + + needsStart = true; + } + else + { + qDebug("SoapySDRInput::start: keep buddy thread"); + } + } + else // first allocation + { + qDebug("SoapySDRInput::start: allocate thread and take ownership"); + soapySDRInputThread = new SoapySDRInputThread(m_deviceShared.m_device, requestedChannel+1); + m_thread = soapySDRInputThread; // take ownership + needsStart = true; + } + + soapySDRInputThread->setFifo(requestedChannel, &m_sampleFifo); + soapySDRInputThread->setLog2Decimation(requestedChannel, m_settings.m_log2Decim); + soapySDRInputThread->setFcPos(requestedChannel, (int) m_settings.m_fcPos); + + if (needsStart) + { + qDebug("SoapySDRInput::start: (re)sart buddy thread"); + soapySDRInputThread->startWork(); + } + + applySettings(m_settings, true); + + qDebug("SoapySDRInput::start: started"); + m_running = true; + + return true; } void SoapySDRInput::stop() { + // This stop method is responsible for managing the thread and channel disabling when the streaming of + // a Rx channel is stopped + // + // If the thread is currently managing only one channel (SI mode). The thread can be just stopped and deleted. + // Then the channel is closed (disabled). + // + // If the thread is currently managing many channels (MI mode) and we are removing the last channel. The transition + // or reduction of MI size is handled by stopping the thread, deleting it and creating a new one + // with one channel less if (and only if) there is still a channel active. + // + // If the thread is currently managing many channels (MI mode) but the channel being stopped is not the last + // channel then the FIFO reference is simply removed from the thread so that it will not stream into this FIFO + // anymore. In this case the channel is not closed (this is managed in the thread object) so that other channels + // can continue with the same configuration. The device continues streaming on this channel but the samples are simply + // dropped (by removing FIFO reference). + // + // Note: this is quite similar to the BladeRF2 stop handling. The main difference is that the channel allocation (enabling) process is + // done in the thread object. + + if (!m_running) { + return; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + SoapySDRInputThread *soapySDRInputThread = findThread(); + + if (soapySDRInputThread == 0) { // no thread allocated + return; + } + + int nbOriginalChannels = soapySDRInputThread->getNbChannels(); + + if (nbOriginalChannels == 1) // SI mode => just stop and delete the thread + { + qDebug("SoapySDRInput::stop: SI mode. Just stop and delete the thread"); + soapySDRInputThread->stopWork(); + delete soapySDRInputThread; + m_thread = 0; + + // remove old thread address from buddies (reset in all buddies) + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) { + ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); + } + } + else if (requestedChannel == nbOriginalChannels - 1) // remove last MI channel => reduce by deleting and re-creating the thread + { + qDebug("SoapySDRInput::stop: MI mode. Reduce by deleting and re-creating the thread"); + soapySDRInputThread->stopWork(); + SampleSinkFifo **fifos = new SampleSinkFifo*[nbOriginalChannels-1]; + unsigned int *log2Decims = new unsigned int[nbOriginalChannels-1]; + int *fcPoss = new int[nbOriginalChannels-1]; + int highestActiveChannelIndex = -1; + + for (int i = 0; i < nbOriginalChannels-1; i++) // save original FIFO references and get the channel with highest index + { + fifos[i] = soapySDRInputThread->getFifo(i); + + if ((soapySDRInputThread->getFifo(i) != 0) && (i > highestActiveChannelIndex)) { + highestActiveChannelIndex = i; + } + + log2Decims[i] = soapySDRInputThread->getLog2Decimation(i); + fcPoss[i] = soapySDRInputThread->getFcPos(i); + } + + delete soapySDRInputThread; + m_thread = 0; + + if (highestActiveChannelIndex >= 0) // there is at least one channel still active + { + soapySDRInputThread = new SoapySDRInputThread(m_deviceShared.m_device, highestActiveChannelIndex+1); + m_thread = soapySDRInputThread; // take ownership + + for (int i = 0; i < highestActiveChannelIndex; i++) // restore original FIFO references + { + soapySDRInputThread->setFifo(i, fifos[i]); + soapySDRInputThread->setLog2Decimation(i, log2Decims[i]); + soapySDRInputThread->setFcPos(i, fcPoss[i]); + } + } + else + { + qDebug("SoapySDRInput::stop: do not re-create thread as there are no more FIFOs active"); + } + + // remove old thread address from buddies (reset in all buddies). The address being held only in the owning source. + const std::vector& sourceBuddies = m_deviceAPI->getSourceBuddies(); + std::vector::const_iterator it = sourceBuddies.begin(); + + for (; it != sourceBuddies.end(); ++it) { + ((DeviceSoapySDRShared*) (*it)->getBuddySharedPtr())->m_source->setThread(0); + } + + if (highestActiveChannelIndex >= 0) { + soapySDRInputThread->startWork(); + } + } + else // remove channel from existing thread + { + qDebug("SoapySDRInput::stop: MI mode. Not changing MI configuration. Just remove FIFO reference"); + soapySDRInputThread->setFifo(requestedChannel, 0); // remove FIFO + } + + m_running = false; } QByteArray SoapySDRInput::serialize() const