Skip to content

Commit 665c748

Browse files
committed
Release camera/video device on source removal, not on destruction
Move the capture-device / media-player teardown into VideoImpl::freeResources() (now overridden by CameraImpl and VideoPlayerImpl) so it can run without destroying the impl object, and route the destructors and loadMovie() through it (re-entrant, null-safe). Add reversible Source::releaseResources()/reacquireResources() hooks. Video releases its device via unloadMovie() and re-opens it via build() (which reuses the stored URI/device id). MappingManager::removeSource() now releases a removed source's device immediately — even though the source may linger in the undo stack — and RemoveSourceCommand::undo() re-acquires it. This lets the same camera be re-opened within a session, replacing the old explicit-destructor hack that caused the double-free crash. Also call removeSource() unconditionally instead of inside Q_ASSERT(), which is compiled out in release builds and would otherwise skip the removal entirely.
1 parent 5a9a620 commit 665c748

10 files changed

Lines changed: 79 additions & 12 deletions

File tree

src/core/CameraImpl.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,30 @@ CameraImpl::CameraImpl()
3131
}
3232

3333
CameraImpl::~CameraImpl()
34+
{
35+
freeResources();
36+
}
37+
38+
void CameraImpl::freeResources()
3439
{
3540
if (_camera)
3641
_camera->stop();
3742
delete _camera;
43+
_camera = nullptr;
3844
delete _captureSession;
45+
_captureSession = nullptr;
3946
delete _cameraSurface;
47+
_cameraSurface = nullptr;
48+
VideoImpl::freeResources();
4049
}
4150

4251
bool CameraImpl::loadMovie(const QString &deviceId)
4352
{
4453
VideoImpl::loadMovie(deviceId);
4554

55+
// Release any previously-opened device before re-acquiring.
56+
freeResources();
57+
4658
// Find the camera device matching the given ID.
4759
QCameraDevice dev;
4860
for (const QCameraDevice& d : QMediaDevices::videoInputs()) {

src/core/CameraImpl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ class CameraImpl : public VideoImpl
4848
bool hasBits() const override { return _cameraSurface && _cameraSurface->isActive(); }
4949
bool bitsHaveChanged() const override { return true; }
5050

51+
protected:
52+
// Stops and releases the capture device (called by unloadMovie() and dtor).
53+
void freeResources() override;
54+
5155
private:
5256
QCamera *_camera;
5357
QMediaCaptureSession *_captureSession;

src/core/Commands.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,10 @@ void RemoveSourceCommand::undo()
270270
if (!_source.isNull())
271271
{
272272
MappingManager& manager = _mainWindow->getMappingManager();
273+
274+
// Re-open any device/player released when the source was removed.
275+
_source->reacquireResources();
276+
273277
uid lastId = manager.addSource(_source);
274278
_mainWindow->addSourceItem(lastId, _source->getIcon(), _source->getName());
275279

src/core/MappingManager.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ bool MappingManager::removeSource(uid sourceId)
133133
Q_ASSERT(idx != -1);
134134
sourceVector.remove(idx);
135135
sourceMap.remove(sourceId);
136+
137+
// Release the source's heavy external resources (e.g. a camera device)
138+
// immediately, even though the object itself may live on in the undo stack.
139+
// reacquireResources() restores them if the removal is undone.
140+
source->releaseResources();
136141
return true;
137142
}
138143
else

src/core/Source.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,22 @@ void Video::rewind()
268268
_impl->resetMovie();
269269
}
270270

271+
void Video::releaseResources()
272+
{
273+
// Release the capture device / media player but keep the impl object, so the
274+
// device is freed as soon as the source is removed (e.g. to let the same
275+
// camera be re-opened) without invalidating the source. See reacquireResources().
276+
if (_impl)
277+
_impl->unloadMovie();
278+
}
279+
280+
void Video::reacquireResources()
281+
{
282+
// Re-open using the stored URI/device id (set by loadMovie()).
283+
if (_impl)
284+
_impl->build();
285+
}
286+
271287
void Video::lockMutex() {
272288
_impl->lockMutex();
273289
}

src/core/Source.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ class Source : public Element
101101
/// Rewinds.
102102
virtual void rewind() {}
103103

104+
/// Releases heavy external resources (capture devices, players, network
105+
/// clients) while keeping the object valid — e.g. when the source is removed
106+
/// but kept in the undo history. Reversible via reacquireResources().
107+
virtual void releaseResources() {}
108+
109+
/// Re-acquires resources released by releaseResources() (e.g. on undo).
110+
virtual void reacquireResources() {}
111+
104112
/// Locks mutex (default = no effect).
105113
virtual void lockMutex() {}
106114

@@ -338,6 +346,11 @@ class Video : public Texture
338346
/// Rewinds.
339347
virtual void rewind();
340348

349+
/// Releases the capture device / player (keeps the object valid).
350+
virtual void releaseResources();
351+
/// Re-opens the capture device / player released by releaseResources().
352+
virtual void reacquireResources();
353+
341354
/// Locks mutex (default = no effect).
342355
virtual void lockMutex();
343356

src/core/VideoImpl.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ class VideoImpl
9999

100100
void resetMovie();
101101

102+
/// Unloads the current movie and releases the capture device / player
103+
/// (reversible via build(), which re-opens the stored URI/device).
104+
void unloadMovie();
105+
102106
/// Locks mutex.
103107
void lockMutex();
104108
/// Unlocks mutex.
@@ -108,7 +112,6 @@ class VideoImpl
108112
bool waitForNextBits(int timeout, const uchar** bits = nullptr);
109113

110114
protected:
111-
void unloadMovie();
112115
virtual void freeResources();
113116

114117
void _setMovieReady(bool ready) { _movieReady = ready; }

src/core/VideoPlayerImpl.cpp

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,31 @@ VideoPlayerImpl::VideoPlayerImpl()
2424
}
2525

2626
VideoPlayerImpl::~VideoPlayerImpl()
27+
{
28+
freeResources();
29+
}
30+
31+
void VideoPlayerImpl::freeResources()
2732
{
2833
if (_player) {
2934
_player->stop();
3035
delete _player;
36+
_player = nullptr;
3137
}
3238
delete _audioOutput;
39+
_audioOutput = nullptr;
3340
delete _videoSink;
41+
_videoSink = nullptr;
42+
_eos = false;
43+
VideoImpl::freeResources();
3444
}
3545

3646
bool VideoPlayerImpl::loadMovie(const QString& path)
3747
{
3848
// Store URI in base class.
3949
VideoImpl::loadMovie(path);
4050

41-
// Tear down any previous player.
42-
if (_player) {
43-
_player->stop();
44-
delete _player;
45-
delete _audioOutput;
46-
delete _videoSink;
47-
}
48-
49-
_eos = false;
51+
// Tear down any previous player and reset frame state.
5052
freeResources();
5153

5254
_player = new QMediaPlayer(this);

src/core/VideoPlayerImpl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ class VideoPlayerImpl : public QObject, public VideoImpl
4747

4848
void update() override;
4949

50+
protected:
51+
// Releases the player/audio/sink (called by unloadMovie() and the destructor).
52+
void freeResources() override;
53+
5054
private slots:
5155
void onVideoFrameChanged(const QVideoFrame& frame);
5256
void onMediaStatusChanged(QMediaPlayer::MediaStatus status);

src/gui/MainWindow.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3380,8 +3380,12 @@ void MainWindow::removeSourceItem(uid sourceId)
33803380
it != sourceLayers.constEnd(); ++it) {
33813381
removeLayerItem(it.key());
33823382
}
3383-
// Remove source from model.
3384-
Q_ASSERT( mappingManager->removeSource(sourceId) );
3383+
// Remove source from model. Call unconditionally (not inside Q_ASSERT, which
3384+
// is compiled out in release builds and would skip the removal + the source's
3385+
// resource release).
3386+
bool sourceRemoved = mappingManager->removeSource(sourceId);
3387+
Q_ASSERT(sourceRemoved);
3388+
Q_UNUSED(sourceRemoved);
33853389

33863390
// Remove associated mapper.
33873391
sourcePropertyPanel->removeWidget(sourceGuis[sourceId]->getPropertiesEditor());

0 commit comments

Comments
 (0)