AVFMediaAssetWriter - fix atomics use

1. No need in two different atomics (m_stopped/m_aborted) - the single
one 'm_state' with states (Idle/Active/Aborted) should be enough.
2. QAtomicInt::load/store actually have relaxed memory ordering semantics,
(not like std::atomic with sequential ordering as the default one)
which is not always appropriate - replace with loadAquire/storeRelease instead.

Change-Id: I4ce8c9ca7556de3d2c7e369b8a05276b2870460c
Reviewed-by: Yoann Lopes <yoann.lopes@qt.io>
This commit is contained in:
Timur Pocheptsov
2016-06-29 14:50:34 +02:00
parent 278fd530f0
commit 17674ddc63
2 changed files with 32 additions and 27 deletions

View File

@@ -47,13 +47,10 @@ QT_BEGIN_NAMESPACE
class AVFMediaRecorderControlIOS; class AVFMediaRecorderControlIOS;
class AVFCameraService; class AVFCameraService;
typedef QAtomicInteger<bool> AVFAtomicBool;
typedef QAtomicInteger<qint64> AVFAtomicInt64; typedef QAtomicInteger<qint64> AVFAtomicInt64;
QT_END_NAMESPACE QT_END_NAMESPACE
// TODO: any reasonable error handling requires smart pointers, otherwise it's getting crappy immediately.
@interface QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate, @interface QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate,
AVCaptureAudioDataOutputSampleBufferDelegate> AVCaptureAudioDataOutputSampleBufferDelegate>
{ {
@@ -78,9 +75,8 @@ QT_END_NAMESPACE
QT_PREPEND_NAMESPACE(AVFMediaRecorderControlIOS) *m_delegate; QT_PREPEND_NAMESPACE(AVFMediaRecorderControlIOS) *m_delegate;
bool m_setStartTime; bool m_setStartTime;
QT_PREPEND_NAMESPACE(AVFAtomicBool) m_stopped;
QT_PREPEND_NAMESPACE(AVFAtomicBool) m_aborted;
QT_PREPEND_NAMESPACE(QAtomicInt) m_state;
QT_PREPEND_NAMESPACE(QMutex) m_writerMutex; QT_PREPEND_NAMESPACE(QMutex) m_writerMutex;
@public @public
QT_PREPEND_NAMESPACE(AVFAtomicInt64) m_durationInMs; QT_PREPEND_NAMESPACE(AVFAtomicInt64) m_durationInMs;

View File

@@ -40,7 +40,6 @@
#include "avfcameradebug.h" #include "avfcameradebug.h"
#include "avfmediacontainercontrol.h" #include "avfmediacontainercontrol.h"
//#include <QtCore/qmutexlocker.h>
#include <QtCore/qmetaobject.h> #include <QtCore/qmetaobject.h>
#include <QtCore/qsysinfo.h> #include <QtCore/qsysinfo.h>
@@ -68,6 +67,13 @@ bool qt_camera_service_isValid(AVFCameraService *service)
return true; return true;
} }
enum WriterState
{
WriterStateIdle,
WriterStateActive,
WriterStateAborted
};
} // unnamed namespace } // unnamed namespace
@interface QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) (PrivateAPI) @interface QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) (PrivateAPI)
@@ -92,8 +98,7 @@ bool qt_camera_service_isValid(AVFCameraService *service)
m_delegate = delegate; m_delegate = delegate;
m_setStartTime = true; m_setStartTime = true;
m_stopped.store(true); m_state.store(WriterStateIdle);
m_aborted.store(false);
m_startTime = kCMTimeInvalid; m_startTime = kCMTimeInvalid;
m_lastTimeStamp = kCMTimeInvalid; m_lastTimeStamp = kCMTimeInvalid;
m_durationInMs.store(0); m_durationInMs.store(0);
@@ -167,15 +172,22 @@ bool qt_camera_service_isValid(AVFCameraService *service)
- (void)start - (void)start
{ {
// To be executed on a writer's queue. // 'start' is executed on a special writer's queue.
// We lock a mutex for the whole function body
// - render control probably was deleted
// 'immediately' after submitting 'start'
// and before 'start' is actually executed ...
// So if we're starting, render control can not
// abort us 'in the middle'.
const QMutexLocker lock(&m_writerMutex); const QMutexLocker lock(&m_writerMutex);
if (m_aborted.load()) if (m_state.load() == WriterStateAborted)
return; return;
[self setQueues]; [self setQueues];
m_setStartTime = true; m_setStartTime = true;
m_stopped.store(false); m_state.storeRelease(WriterStateActive);
[m_assetWriter startWriting]; [m_assetWriter startWriting];
AVCaptureSession *session = m_service->session()->captureSession(); AVCaptureSession *session = m_service->session()->captureSession();
if (!session.running) if (!session.running)
@@ -187,26 +199,23 @@ bool qt_camera_service_isValid(AVFCameraService *service)
// To be executed on a writer's queue. // To be executed on a writer's queue.
{ {
const QMutexLocker lock(&m_writerMutex); const QMutexLocker lock(&m_writerMutex);
if (m_aborted.load()) if (m_state.load() != WriterStateActive)
return; return;
if (m_stopped.load())
return;
m_stopped.store(true);
} }
[m_assetWriter finishWritingWithCompletionHandler:^{ [m_assetWriter finishWritingWithCompletionHandler:^{
// This block is async, so by the time it's executed, // This block is async, so by the time it's executed,
// it's possible that render control was deleted already ... // it's possible that render control was deleted already ...
// We lock so that nobody can call 'abort' in the middle ...
const QMutexLocker lock(&m_writerMutex); const QMutexLocker lock(&m_writerMutex);
if (m_aborted.load()) if (m_state.load() != WriterStateActive)
return; return;
AVCaptureSession *session = m_service->session()->captureSession(); AVCaptureSession *session = m_service->session()->captureSession();
[session stopRunning]; [session stopRunning];
[session removeOutput:m_audioOutput]; [session removeOutput:m_audioOutput];
[session removeInput:m_audioInput]; [session removeInput:m_audioInput];
m_state.storeRelease(WriterStateIdle);
QMetaObject::invokeMethod(m_delegate, "assetWriterFinished", Qt::QueuedConnection); QMetaObject::invokeMethod(m_delegate, "assetWriterFinished", Qt::QueuedConnection);
}]; }];
} }
@@ -216,9 +225,10 @@ bool qt_camera_service_isValid(AVFCameraService *service)
// To be executed on any thread (presumably, it's the main thread), // To be executed on any thread (presumably, it's the main thread),
// prevents writer from accessing any shared object. // prevents writer from accessing any shared object.
const QMutexLocker lock(&m_writerMutex); const QMutexLocker lock(&m_writerMutex);
m_aborted.store(true); if (m_state.fetchAndStoreRelease(WriterStateAborted) != WriterStateActive) {
if (m_stopped.load()) // Not recording, nothing to stop.
return; return;
}
[m_assetWriter finishWritingWithCompletionHandler:^{ [m_assetWriter finishWritingWithCompletionHandler:^{
}]; }];
@@ -231,7 +241,7 @@ bool qt_camera_service_isValid(AVFCameraService *service)
Q_ASSERT(sampleBuffer); Q_ASSERT(sampleBuffer);
const QMutexLocker lock(&m_writerMutex); const QMutexLocker lock(&m_writerMutex);
if (m_aborted.load() || m_stopped.load()) if (m_state.load() != WriterStateActive)
return; return;
QMetaObject::invokeMethod(m_delegate, "assetWriterStarted", Qt::QueuedConnection); QMetaObject::invokeMethod(m_delegate, "assetWriterStarted", Qt::QueuedConnection);
@@ -248,7 +258,7 @@ bool qt_camera_service_isValid(AVFCameraService *service)
Q_ASSERT(sampleBuffer); Q_ASSERT(sampleBuffer);
// This code is executed only on a writer's queue. // This code is executed only on a writer's queue.
if (!m_aborted.load() && !m_stopped.load()) { if (m_state.loadAcquire() == WriterStateActive) {
if (m_setStartTime) if (m_setStartTime)
[self setStartTimeFrom:sampleBuffer]; [self setStartTimeFrom:sampleBuffer];
@@ -264,11 +274,10 @@ bool qt_camera_service_isValid(AVFCameraService *service)
- (void)writeAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer - (void)writeAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer
{ {
// This code is executed only on a writer's queue.
// it does not touch any shared/external data.
Q_ASSERT(sampleBuffer); Q_ASSERT(sampleBuffer);
if (!m_aborted.load() && !m_stopped.load()) { // This code is executed only on a writer's queue.
if (m_state.loadAcquire() == WriterStateActive) {
if (m_setStartTime) if (m_setStartTime)
[self setStartTimeFrom:sampleBuffer]; [self setStartTimeFrom:sampleBuffer];
@@ -290,7 +299,7 @@ bool qt_camera_service_isValid(AVFCameraService *service)
// This method can be called on either video or audio queue, // This method can be called on either video or audio queue,
// never on a writer's queue, it needs access to a shared data, so // never on a writer's queue, it needs access to a shared data, so
// lock is required. // lock is required.
if (m_stopped.load()) if (m_state.loadAcquire() != WriterStateActive)
return; return;
if (!CMSampleBufferDataIsReady(sampleBuffer)) { if (!CMSampleBufferDataIsReady(sampleBuffer)) {
@@ -302,7 +311,7 @@ bool qt_camera_service_isValid(AVFCameraService *service)
if (captureOutput != m_audioOutput.data()) { if (captureOutput != m_audioOutput.data()) {
const QMutexLocker lock(&m_writerMutex); const QMutexLocker lock(&m_writerMutex);
if (m_aborted.load() || m_stopped.load()) { if (m_state.load() != WriterStateActive) {
CFRelease(sampleBuffer); CFRelease(sampleBuffer);
return; return;
} }