Clean-up of OS X podcast recording; fixed frames sometimes carrying over from one video to the next

preferencesAboutTextFull
Craig Watson 9 years ago
parent 5a51dbd908
commit 17a08d35e4
  1. 73
      src/podcast/quicktime/UBQuickTimeFile.h
  2. 52
      src/podcast/quicktime/UBQuickTimeFile.mm
  3. 20
      src/podcast/quicktime/UBQuickTimeVideoEncoder.cpp

@ -59,6 +59,14 @@ class UBQuickTimeFile : public QThread
Q_OBJECT; Q_OBJECT;
public: public:
struct VideoFrame
{
CVPixelBufferRef buffer;
long timestamp;
};
static QWaitCondition frameBufferNotEmpty;
UBQuickTimeFile(QObject * pParent = 0); UBQuickTimeFile(QObject * pParent = 0);
virtual ~UBQuickTimeFile(); virtual ~UBQuickTimeFile();
@ -69,23 +77,11 @@ class UBQuickTimeFile : public QThread
void stop(); void stop();
CVPixelBufferRef newPixelBuffer(); CVPixelBufferRef newPixelBuffer();
void enqueueVideoFrame(VideoFrame frame);
bool isCompressionSessionRunning() { return mCompressionSessionRunning; } bool isCompressionSessionRunning() { return mCompressionSessionRunning; }
QString lastErrorMessage() const { return mLastErrorMessage; } QString lastErrorMessage() const { return mLastErrorMessage; }
void endSession();
struct VideoFrame
{
CVPixelBufferRef buffer;
long timestamp;
};
static QQueue<VideoFrame> frameQueue;
static QMutex frameQueueMutex;
static QWaitCondition frameBufferNotEmpty;
signals: signals:
void audioLevelChanged(quint8 level); void audioLevelChanged(quint8 level);
void compressionSessionStarted(); void compressionSessionStarted();
@ -98,44 +94,55 @@ class UBQuickTimeFile : public QThread
void enqueueAudioBuffer(void* pBuffer, long pLength); void enqueueAudioBuffer(void* pBuffer, long pLength);
private: private:
QString mLastErrorMessage;
bool beginSession(); // Format information
void setLastErrorMessage(const QString& error);
void appendVideoFrame(CVPixelBufferRef pixelBuffer, long msTimeStamp);
bool appendSampleBuffer(CMSampleBufferRef sampleBuffer);
QSize mFrameSize;
QString mVideoFileName; QString mVideoFileName;
QSize mFrameSize;
long mTimeScale;
bool mRecordAudio;
QString mAudioRecordingDeviceName;
// Video/audio encoders and associated objects
AssetWriterPTR mVideoWriter; AssetWriterPTR mVideoWriter;
AssetWriterInputPTR mVideoWriterInput; AssetWriterInputPTR mVideoWriterInput;
AssetWriterInputAdaptorPTR mAdaptor; AssetWriterInputAdaptorPTR mAdaptor;
AssetWriterInputPTR mAudioWriterInput; AssetWriterInputPTR mAudioWriterInput;
// Audio recorder
QPointer<UBAudioQueueRecorder> mWaveRecorder; QPointer<UBAudioQueueRecorder> mWaveRecorder;
CMAudioFormatDescriptionRef mAudioFormatDescription;
// Variables used during encoding
CFAbsoluteTime mStartTime; CFAbsoluteTime mStartTime;
CMTime mLastFrameTimestamp;
CMAudioFormatDescriptionRef mAudioFormatDescription;
long mTimeScale;
bool mRecordAudio;
volatile bool mShouldStopCompression; volatile bool mShouldStopCompression;
volatile bool mCompressionSessionRunning; volatile bool mCompressionSessionRunning;
QString mLastErrorMessage; // Dispatch queues to handle passing data to the A/V encoders
QString mAudioRecordingDeviceName;
dispatch_queue_t mVideoDispatchQueue; dispatch_queue_t mVideoDispatchQueue;
dispatch_queue_t mAudioDispatchQueue; dispatch_queue_t mAudioDispatchQueue;
static QQueue<CMSampleBufferRef> audioQueue; // Queues for frames and audio buffers to be encoded
static QMutex audioQueueMutex; QQueue<VideoFrame> frameQueue;
QQueue<CMSampleBufferRef> audioQueue;
QMutex frameQueueMutex;
QMutex audioQueueMutex;
QMutex audioWriterMutex;
// Private functions
void setLastErrorMessage(const QString& error);
bool beginSession();
void endSession();
void appendFrameToVideo(CVPixelBufferRef pixelBuffer, long msTimeStamp);
bool appendSampleBuffer(CMSampleBufferRef sampleBuffer);
static QMutex audioWriterMutex;
}; };
#endif /* UBQUICKTIMEFILE_H_ */ #endif /* UBQUICKTIMEFILE_H_ */

@ -37,15 +37,8 @@
#include "core/memcheck.h" #include "core/memcheck.h"
QQueue<UBQuickTimeFile::VideoFrame> UBQuickTimeFile::frameQueue;
QMutex UBQuickTimeFile::frameQueueMutex;
QWaitCondition UBQuickTimeFile::frameBufferNotEmpty; QWaitCondition UBQuickTimeFile::frameBufferNotEmpty;
QQueue<CMSampleBufferRef> UBQuickTimeFile::audioQueue;
QMutex UBQuickTimeFile::audioQueueMutex;
QMutex UBQuickTimeFile::audioWriterMutex;
UBQuickTimeFile::UBQuickTimeFile(QObject * pParent) UBQuickTimeFile::UBQuickTimeFile(QObject * pParent)
: QThread(pParent) : QThread(pParent)
, mVideoWriter(0) , mVideoWriter(0)
@ -65,13 +58,14 @@ UBQuickTimeFile::UBQuickTimeFile(QObject * pParent)
UBQuickTimeFile::~UBQuickTimeFile() UBQuickTimeFile::~UBQuickTimeFile()
{ {
// destruction of mWaveRecorder is handled by endSession()
} }
bool UBQuickTimeFile::init(const QString& pVideoFileName, const QString& pProfileData, int pFramesPerSecond bool UBQuickTimeFile::init(const QString& pVideoFileName, const QString& pProfileData, int pFramesPerSecond
, const QSize& pFrameSize, bool pRecordAudio, const QString& audioRecordingDevice) , const QSize& pFrameSize, bool pRecordAudio, const QString& audioRecordingDevice)
{ {
Q_UNUSED(pProfileData);
Q_UNUSED(pFramesPerSecond);
mFrameSize = pFrameSize; mFrameSize = pFrameSize;
mVideoFileName = pVideoFileName; mVideoFileName = pVideoFileName;
mRecordAudio = pRecordAudio; mRecordAudio = pRecordAudio;
@ -109,8 +103,9 @@ void UBQuickTimeFile::run()
!frameQueue.isEmpty() && !frameQueue.isEmpty() &&
[mVideoWriterInput isReadyForMoreMediaData]) [mVideoWriterInput isReadyForMoreMediaData])
{ {
// in this case the last few frames may be dropped if the queue isn't empty...
VideoFrame frame = frameQueue.dequeue(); VideoFrame frame = frameQueue.dequeue();
appendVideoFrame(frame.buffer, frame.timestamp); appendFrameToVideo(frame.buffer, frame.timestamp);
} }
frameQueueMutex.unlock(); frameQueueMutex.unlock();
@ -174,7 +169,6 @@ bool UBQuickTimeFile::beginSession()
[NSNumber numberWithInt:frameHeight], AVVideoHeightKey, [NSNumber numberWithInt:frameHeight], AVVideoHeightKey,
nil]; nil];
mVideoWriterInput = [[AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo mVideoWriterInput = [[AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
outputSettings:videoSettings] retain]; outputSettings:videoSettings] retain];
NSCParameterAssert(mVideoWriterInput); NSCParameterAssert(mVideoWriterInput);
@ -253,7 +247,7 @@ bool UBQuickTimeFile::beginSession()
[mVideoWriter startSessionAtSourceTime:CMTimeMake(0, mTimeScale)]; [mVideoWriter startSessionAtSourceTime:CMTimeMake(0, mTimeScale)];
mStartTime = CFAbsoluteTimeGetCurrent(); // used for audio timestamp calculation mStartTime = CFAbsoluteTimeGetCurrent(); // used for audio timestamp calculation
mLastFrameTimestamp = CMTimeMake(0, mTimeScale);
return (mVideoWriter != nil) && (mVideoWriterInput != nil) && canStartWriting; return (mVideoWriter != nil) && (mVideoWriterInput != nil) && canStartWriting;
} }
@ -267,6 +261,8 @@ void UBQuickTimeFile::endSession()
[mVideoWriterInput markAsFinished]; [mVideoWriterInput markAsFinished];
if (mAudioWriterInput != 0)
[mAudioWriterInput markAsFinished];
[mVideoWriter finishWritingWithCompletionHandler:^{ [mVideoWriter finishWritingWithCompletionHandler:^{
[mAdaptor release]; [mAdaptor release];
@ -319,32 +315,48 @@ CVPixelBufferRef UBQuickTimeFile::newPixelBuffer()
{ {
CVPixelBufferRef pixelBuffer = 0; CVPixelBufferRef pixelBuffer = 0;
if(CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, mAdaptor.pixelBufferPool, &pixelBuffer) != kCVReturnSuccess) CVReturn result = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, mAdaptor.pixelBufferPool, &pixelBuffer);
{
setLastErrorMessage("Could not retrieve CV buffer from pool"); if (result != kCVReturnSuccess) {
setLastErrorMessage("Could not retrieve CV buffer from pool (error " + QString::number(result) + ")");
return 0; return 0;
} }
return pixelBuffer; return pixelBuffer;
} }
void UBQuickTimeFile::enqueueVideoFrame(VideoFrame frame)
{
frameQueueMutex.lock();
frameQueue.enqueue(frame);
frameQueueMutex.unlock();
}
/** /**
* \brief Add a frame to the pixel buffer adaptor * \brief Add a frame to the pixel buffer adaptor
* \param pixelBuffer The CVPixelBufferRef (video frame) to add to the movie * \param pixelBuffer The CVPixelBufferRef (video frame) to add to the movie
* \param msTimeStamp Timestamp, in milliseconds, of the frame * \param msTimeStamp Timestamp, in milliseconds, of the frame
*/ */
void UBQuickTimeFile::appendVideoFrame(CVPixelBufferRef pixelBuffer, long msTimeStamp) void UBQuickTimeFile::appendFrameToVideo(CVPixelBufferRef pixelBuffer, long msTimeStamp)
{ {
//qDebug() << "appending video frame"; //qDebug() << "appending video frame";
CMTime t = CMTimeMake((msTimeStamp * mTimeScale / 1000.0), mTimeScale); CMTime t = CMTimeMake((msTimeStamp * mTimeScale / 1000.0), mTimeScale);
bool added = [mAdaptor appendPixelBuffer: pixelBuffer // The timestamp must be both valid and larger than the previous frame's timestamp
withPresentationTime: t]; if (CMTIME_IS_VALID(t) && CMTimeCompare(t, mLastFrameTimestamp) == 1) {
if (!added) bool added = [mAdaptor appendPixelBuffer: pixelBuffer
setLastErrorMessage(QString("Could not encode frame at time %1").arg(msTimeStamp)); withPresentationTime: t];
if (!added)
setLastErrorMessage(QString("Could not encode frame at time %1").arg(msTimeStamp));
mLastFrameTimestamp = t;
}
else {
qDebug() << "Frame dropped; timestamp was smaller or equal to previous frame's timestamp of: "
<< mLastFrameTimestamp.value << "/" << mLastFrameTimestamp.timescale;
}
CVPixelBufferRelease(pixelBuffer); CVPixelBufferRelease(pixelBuffer);
} }

@ -97,15 +97,12 @@ void UBQuickTimeVideoEncoder::compressionFinished()
void UBQuickTimeVideoEncoder::newPixmap(const QImage& pImage, long timestamp) void UBQuickTimeVideoEncoder::newPixmap(const QImage& pImage, long timestamp)
{ {
//qDebug() << "New Frame at ms" << timestamp; //qDebug() << "New Frame at ms" << timestamp;
if(mQuickTimeCompressionSession.isCompressionSessionRunning()) if(mQuickTimeCompressionSession.isCompressionSessionRunning()) {
{ if(mPendingImageFrames.length() > 0) {
if(mPendingImageFrames.length() > 0) foreach(ImageFrame frame, mPendingImageFrames) {
{ encodeFrame(frame.image, frame.timestamp);
foreach(ImageFrame frame, mPendingImageFrames)
{
encodeFrame(frame.image, frame.timestamp);
} }
mPendingImageFrames.clear(); mPendingImageFrames.clear();
@ -115,8 +112,7 @@ void UBQuickTimeVideoEncoder::newPixmap(const QImage& pImage, long timestamp)
UBQuickTimeFile::frameBufferNotEmpty.wakeAll(); UBQuickTimeFile::frameBufferNotEmpty.wakeAll();
} }
else else {
{
qDebug() << "queuing frame, compressor not ready"; qDebug() << "queuing frame, compressor not ready";
ImageFrame frame; ImageFrame frame;
@ -172,9 +168,7 @@ void UBQuickTimeVideoEncoder::encodeFrame(const QImage& pImage, long timestamp)
videoFrame.buffer = pixelBuffer; videoFrame.buffer = pixelBuffer;
videoFrame.timestamp = timestamp; videoFrame.timestamp = timestamp;
UBQuickTimeFile::frameQueueMutex.lock(); mQuickTimeCompressionSession.enqueueVideoFrame(videoFrame);
UBQuickTimeFile::frameQueue.enqueue(videoFrame);
UBQuickTimeFile::frameQueueMutex.unlock();
} }

Loading…
Cancel
Save