diff --git a/src/core/coreav.cpp b/src/core/coreav.cpp index ba99d1d1c..60527086b 100644 --- a/src/core/coreav.cpp +++ b/src/core/coreav.cpp @@ -260,6 +260,13 @@ void CoreAV::sendCallVideo(uint32_t callId, shared_ptr vframe) || !(call.state & TOXAV_FRIEND_CALL_STATE_ACCEPTING_V)) return; + if (call.nullVideoBitrate) + { + qDebug() << "Restarting video stream to friend"<toVpxImage(); if (frame->fmt == VPX_IMG_FMT_NONE) @@ -406,6 +413,17 @@ void CoreAV::resetCallSources() } } +void CoreAV::sendNoVideo() +{ + // We don't change the audio bitrate, but we signal that we're not sending video anymore + qDebug() << "CoreAV: Signaling end of video sending"; + for (ToxFriendCall& call : calls) + { + toxav_bit_rate_set(toxav, call.callId, -1, 0, nullptr); + call.nullVideoBitrate = true; + } +} + void CoreAV::callCallback(ToxAV* toxav, uint32_t friendNum, bool audio, bool video, void *_self) { CoreAV* self = static_cast(_self); @@ -481,6 +499,20 @@ void CoreAV::stateCallback(ToxAV* toxav, uint32_t friendNum, uint32_t state, voi call.inactive = false; emit self->avStart(friendNum, call.videoEnabled); } + else if ((call.state & TOXAV_FRIEND_CALL_STATE_SENDING_V) + && !(state & TOXAV_FRIEND_CALL_STATE_SENDING_V)) + { + qDebug() << "Friend"<stopSource(); + } + else if (!(call.state & TOXAV_FRIEND_CALL_STATE_SENDING_V) + && (state & TOXAV_FRIEND_CALL_STATE_SENDING_V)) + { + // Workaround toxav sometimes firing callbacks for "send last frame" -> "stop sending video" + // out of orders (even though they were sent in order by the other end). + // We simply stop the videoSource from emitting anything while the other end says it's not sending + call.videoSource->restartSource(); + } call.state = static_cast(state); } diff --git a/src/core/coreav.h b/src/core/coreav.h index cfcd222b8..e331196de 100644 --- a/src/core/coreav.h +++ b/src/core/coreav.h @@ -66,6 +66,7 @@ public: VideoSource* getVideoSourceFromCall(int callNumber); ///< Get a call's video source void resetCallSources(); ///< Forces to regenerate each call's audio sources + void sendNoVideo(); ///< Signal to all peers that we're not sending video anymore. The next frame sent cancels this. void joinGroupCall(int groupNum); ///< Starts a call in an existing AV groupchat. Call from the GUI thread. void leaveGroupCall(int groupNum); ///< Will not leave the group, just stop the call. Call from the GUI thread. diff --git a/src/core/toxcall.cpp b/src/core/toxcall.cpp index e2d837bbb..d5f3fc8be 100644 --- a/src/core/toxcall.cpp +++ b/src/core/toxcall.cpp @@ -117,7 +117,7 @@ void ToxFriendCall::stopTimeout() ToxFriendCall::ToxFriendCall(uint32_t FriendNum, bool VideoEnabled, CoreAV& av) : ToxCall(FriendNum), - videoEnabled{VideoEnabled}, videoSource{nullptr}, + videoEnabled{VideoEnabled}, nullVideoBitrate{false}, videoSource{nullptr}, state{static_cast(0)}, av{&av}, timeoutTimer{nullptr} { @@ -144,8 +144,9 @@ ToxFriendCall::ToxFriendCall(uint32_t FriendNum, bool VideoEnabled, CoreAV& av) ToxFriendCall::ToxFriendCall(ToxFriendCall&& other) : ToxCall(move(other)), - videoEnabled{other.videoEnabled}, videoSource{other.videoSource}, - state{other.state}, av{other.av}, timeoutTimer{other.timeoutTimer} + videoEnabled{other.videoEnabled}, nullVideoBitrate{other.nullVideoBitrate}, + videoSource{other.videoSource}, state{other.state}, + av{other.av}, timeoutTimer{other.timeoutTimer} { other.videoEnabled = false; other.videoSource = nullptr; @@ -182,6 +183,7 @@ const ToxFriendCall& ToxFriendCall::operator=(ToxFriendCall&& other) timeoutTimer = other.timeoutTimer; other.timeoutTimer = nullptr; av = other.av; + nullVideoBitrate = other.nullVideoBitrate; return *this; } diff --git a/src/core/toxcall.h b/src/core/toxcall.h index aa84ced8e..cc55b1f41 100644 --- a/src/core/toxcall.h +++ b/src/core/toxcall.h @@ -58,6 +58,7 @@ struct ToxFriendCall : public ToxCall const ToxFriendCall& operator=(ToxFriendCall&& other) noexcept; bool videoEnabled; ///< True if our user asked for a video call, sending and recving + bool nullVideoBitrate; ///< True if our video bitrate is zero, i.e. if the device is closed CoreVideoSource* videoSource; TOXAV_FRIEND_CALL_STATE state; ///< State of the peer (not ours!) diff --git a/src/video/corevideosource.cpp b/src/video/corevideosource.cpp index c01f0cb18..00576dff7 100644 --- a/src/video/corevideosource.cpp +++ b/src/video/corevideosource.cpp @@ -26,12 +26,15 @@ extern "C" { CoreVideoSource::CoreVideoSource() : subscribers{0}, deleteOnClose{false}, - biglock{false} + biglock{false}, stopped{false} { } void CoreVideoSource::pushFrame(const vpx_image_t* vpxframe) { + if (stopped) + return; + // Fast lock { bool expected = false; @@ -126,3 +129,14 @@ void CoreVideoSource::setDeleteOnClose(bool newstate) deleteOnClose = newstate; biglock = false; } + +void CoreVideoSource::stopSource() +{ + stopped = true; + emit sourceStopped(); +} + +void CoreVideoSource::restartSource() +{ + stopped = false; +} diff --git a/src/video/corevideosource.h b/src/video/corevideosource.h index 2f2e49210..888cc063a 100644 --- a/src/video/corevideosource.h +++ b/src/video/corevideosource.h @@ -44,10 +44,16 @@ private: /// If true, self-delete after the last suscriber is gone void setDeleteOnClose(bool newstate); + /// Stopping the source will block any pushFrame calls from doing anything + /// See the callers in CoreAV for the rationale + void stopSource(); + void restartSource(); + private: std::atomic_int subscribers; ///< Number of suscribers std::atomic_bool deleteOnClose; ///< If true, self-delete after the last suscriber is gone std::atomic_bool biglock; ///< Fast lock + std::atomic_bool stopped; friend class CoreAV; friend struct ToxFriendCall; diff --git a/src/video/videosource.h b/src/video/videosource.h index 02f79701a..476e7c885 100644 --- a/src/video/videosource.h +++ b/src/video/videosource.h @@ -41,6 +41,9 @@ public: signals: void frameAvailable(std::shared_ptr frame); + /// Emitted when the source is stopped for an indefinite amount of time, + /// but might restart sending frames again later + void sourceStopped(); }; #endif // VIDEOSOURCE_H diff --git a/src/video/videosurface.cpp b/src/video/videosurface.cpp index 4cff9b134..0c3bf00a3 100644 --- a/src/video/videosurface.cpp +++ b/src/video/videosurface.cpp @@ -102,6 +102,7 @@ void VideoSurface::subscribe() { source->subscribe(); connect(source, &VideoSource::frameAvailable, this, &VideoSurface::onNewFrameAvailable); + connect(source, &VideoSource::sourceStopped, this, &VideoSurface::onSourceStopped); } } @@ -124,6 +125,7 @@ void VideoSurface::unsubscribe() source->unsubscribe(); disconnect(source, &VideoSource::frameAvailable, this, &VideoSurface::onNewFrameAvailable); + disconnect(source, &VideoSource::sourceStopped, this, &VideoSurface::onSourceStopped); } void VideoSurface::onNewFrameAvailable(std::shared_ptr newFrame) @@ -148,6 +150,13 @@ void VideoSurface::onNewFrameAvailable(std::shared_ptr newFrame) update(); } +void VideoSurface::onSourceStopped() +{ + // If the source's stream is on hold, just revert back to the avatar view + lastFrame.reset(); + update(); +} + void VideoSurface::paintEvent(QPaintEvent*) { lock(); diff --git a/src/video/videosurface.h b/src/video/videosurface.h index 533776b14..a460fe42b 100644 --- a/src/video/videosurface.h +++ b/src/video/videosurface.h @@ -55,6 +55,7 @@ protected: private slots: void onNewFrameAvailable(std::shared_ptr newFrame); + void onSourceStopped(); private: void recalulateBounds(); diff --git a/src/widget/form/settings/avform.cpp b/src/widget/form/settings/avform.cpp index a2a82b78e..9db9f844c 100644 --- a/src/widget/form/settings/avform.cpp +++ b/src/widget/form/settings/avform.cpp @@ -25,6 +25,8 @@ #include "src/video/cameradevice.h" #include "src/video/videosurface.h" #include "src/widget/translator.h" +#include "src/core/core.h" +#include "src/core/coreav.h" #if defined(__APPLE__) && defined(__MACH__) #include @@ -210,6 +212,8 @@ void AVForm::onVideoDevChanged(int index) camera.open(dev); killVideoSurface(); createVideoSurface(); + if (dev == "none") + Core::getInstance()->getAv()->sendNoVideo(); } void AVForm::hideEvent(QHideEvent *)