|
|
@ -41,18 +41,15 @@ QQueue<UBQuickTimeFile::VideoFrame> UBQuickTimeFile::frameQueue; |
|
|
|
QMutex UBQuickTimeFile::frameQueueMutex; |
|
|
|
QMutex UBQuickTimeFile::frameQueueMutex; |
|
|
|
QWaitCondition UBQuickTimeFile::frameBufferNotEmpty; |
|
|
|
QWaitCondition UBQuickTimeFile::frameBufferNotEmpty; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
UBQuickTimeFile::UBQuickTimeFile(QObject * pParent) |
|
|
|
UBQuickTimeFile::UBQuickTimeFile(QObject * pParent) |
|
|
|
: QThread(pParent) |
|
|
|
: QThread(pParent) |
|
|
|
, mVideoWriter(0) |
|
|
|
, mVideoWriter(0) |
|
|
|
, mVideoWriterInput(0) |
|
|
|
, mVideoWriterInput(0) |
|
|
|
, mAdaptor(0) |
|
|
|
, mAdaptor(0) |
|
|
|
, mFramesPerSecond(-1) |
|
|
|
|
|
|
|
, mTimeScale(1000) |
|
|
|
, mTimeScale(1000) |
|
|
|
, mRecordAudio(true) |
|
|
|
, mRecordAudio(true) |
|
|
|
, mShouldStopCompression(false) |
|
|
|
, mShouldStopCompression(false) |
|
|
|
, mCompressionSessionRunning(false) |
|
|
|
, mCompressionSessionRunning(false) |
|
|
|
, mPendingFrames(0) |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -67,10 +64,8 @@ bool UBQuickTimeFile::init(const QString& pVideoFileName, const QString& pProfil |
|
|
|
, const QSize& pFrameSize, bool pRecordAudio, const QString& audioRecordingDevice) |
|
|
|
, const QSize& pFrameSize, bool pRecordAudio, const QString& audioRecordingDevice) |
|
|
|
{ |
|
|
|
{ |
|
|
|
mFrameSize = pFrameSize; |
|
|
|
mFrameSize = pFrameSize; |
|
|
|
mFramesPerSecond = pFramesPerSecond; |
|
|
|
|
|
|
|
mVideoFileName = pVideoFileName; |
|
|
|
mVideoFileName = pVideoFileName; |
|
|
|
mRecordAudio = pRecordAudio; |
|
|
|
mRecordAudio = pRecordAudio; |
|
|
|
mSpatialQuality = pProfileData; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (mRecordAudio) |
|
|
|
if (mRecordAudio) |
|
|
|
mAudioRecordingDeviceName = audioRecordingDevice; |
|
|
|
mAudioRecordingDeviceName = audioRecordingDevice; |
|
|
@ -88,7 +83,6 @@ bool UBQuickTimeFile::init(const QString& pVideoFileName, const QString& pProfil |
|
|
|
void UBQuickTimeFile::run() |
|
|
|
void UBQuickTimeFile::run() |
|
|
|
{ |
|
|
|
{ |
|
|
|
mShouldStopCompression = false; |
|
|
|
mShouldStopCompression = false; |
|
|
|
mPendingFrames = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!beginSession()) |
|
|
|
if (!beginSession()) |
|
|
|
return; |
|
|
|
return; |
|
|
@ -96,7 +90,8 @@ void UBQuickTimeFile::run() |
|
|
|
mCompressionSessionRunning = true; |
|
|
|
mCompressionSessionRunning = true; |
|
|
|
emit compressionSessionStarted(); |
|
|
|
emit compressionSessionStarted(); |
|
|
|
|
|
|
|
|
|
|
|
do { |
|
|
|
do { |
|
|
|
|
|
|
|
// Video |
|
|
|
frameQueueMutex.lock(); |
|
|
|
frameQueueMutex.lock(); |
|
|
|
|
|
|
|
|
|
|
|
frameBufferNotEmpty.wait(&UBQuickTimeFile::frameQueueMutex); |
|
|
|
frameBufferNotEmpty.wait(&UBQuickTimeFile::frameQueueMutex); |
|
|
@ -118,6 +113,7 @@ void UBQuickTimeFile::run() |
|
|
|
} |
|
|
|
} |
|
|
|
else |
|
|
|
else |
|
|
|
frameQueueMutex.unlock(); |
|
|
|
frameQueueMutex.unlock(); |
|
|
|
|
|
|
|
|
|
|
|
} while(!mShouldStopCompression); |
|
|
|
} while(!mShouldStopCompression); |
|
|
|
|
|
|
|
|
|
|
|
endSession(); |
|
|
|
endSession(); |
|
|
@ -127,7 +123,7 @@ void UBQuickTimeFile::run() |
|
|
|
/** |
|
|
|
/** |
|
|
|
* \brief Begin the recording session; initialize the audio/video writer |
|
|
|
* \brief Begin the recording session; initialize the audio/video writer |
|
|
|
* \return true if the session was initialized successfully |
|
|
|
* \return true if the session was initialized successfully |
|
|
|
* |
|
|
|
* |
|
|
|
* This function initializes the AVAssetWriter and associated video and audio inputs. |
|
|
|
* This function initializes the AVAssetWriter and associated video and audio inputs. |
|
|
|
* Video is encoded as H264; audio is encoded as AAC. |
|
|
|
* Video is encoded as H264; audio is encoded as AAC. |
|
|
|
*/ |
|
|
|
*/ |
|
|
@ -141,7 +137,7 @@ bool UBQuickTimeFile::beginSession() |
|
|
|
qDebug() << "Podcast video URL invalid; not recording"; |
|
|
|
qDebug() << "Podcast video URL invalid; not recording"; |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Create and check the assetWriter |
|
|
|
// Create and check the assetWriter |
|
|
|
mVideoWriter = [[AVAssetWriter assetWriterWithURL:outputUrl |
|
|
|
mVideoWriter = [[AVAssetWriter assetWriterWithURL:outputUrl |
|
|
|
fileType:AVFileTypeQuickTimeMovie |
|
|
|
fileType:AVFileTypeQuickTimeMovie |
|
|
@ -149,7 +145,7 @@ bool UBQuickTimeFile::beginSession() |
|
|
|
NSCParameterAssert(mVideoWriter); |
|
|
|
NSCParameterAssert(mVideoWriter); |
|
|
|
|
|
|
|
|
|
|
|
mVideoWriter.movieTimeScale = mTimeScale; |
|
|
|
mVideoWriter.movieTimeScale = mTimeScale; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Video |
|
|
|
// Video |
|
|
@ -158,7 +154,7 @@ bool UBQuickTimeFile::beginSession() |
|
|
|
int frameWidth = mFrameSize.width(); |
|
|
|
int frameWidth = mFrameSize.width(); |
|
|
|
int frameHeight = mFrameSize.height(); |
|
|
|
int frameHeight = mFrameSize.height(); |
|
|
|
|
|
|
|
|
|
|
|
// Create the input and check it |
|
|
|
// Create the input and check it |
|
|
|
NSDictionary * videoSettings = [NSDictionary dictionaryWithObjectsAndKeys: |
|
|
|
NSDictionary * videoSettings = [NSDictionary dictionaryWithObjectsAndKeys: |
|
|
|
AVVideoCodecH264, AVVideoCodecKey, |
|
|
|
AVVideoCodecH264, AVVideoCodecKey, |
|
|
|
[NSNumber numberWithInt:frameWidth], AVVideoWidthKey, |
|
|
|
[NSNumber numberWithInt:frameWidth], AVVideoWidthKey, |
|
|
@ -167,7 +163,7 @@ bool UBQuickTimeFile::beginSession() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mVideoWriterInput = [[AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo |
|
|
|
mVideoWriterInput = [[AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo |
|
|
|
outputSettings:videoSettings] retain]; |
|
|
|
outputSettings:videoSettings] retain]; |
|
|
|
NSCParameterAssert(mVideoWriterInput); |
|
|
|
NSCParameterAssert(mVideoWriterInput); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -193,7 +189,7 @@ bool UBQuickTimeFile::beginSession() |
|
|
|
|
|
|
|
|
|
|
|
if(mRecordAudio) { |
|
|
|
if(mRecordAudio) { |
|
|
|
mWaveRecorder = new UBAudioQueueRecorder(); |
|
|
|
mWaveRecorder = new UBAudioQueueRecorder(); |
|
|
|
|
|
|
|
|
|
|
|
// Get the audio format description from mWaveRecorder |
|
|
|
// Get the audio format description from mWaveRecorder |
|
|
|
CMAudioFormatDescriptionCreate(kCFAllocatorDefault, mWaveRecorder->audioFormat(), |
|
|
|
CMAudioFormatDescriptionCreate(kCFAllocatorDefault, mWaveRecorder->audioFormat(), |
|
|
|
0, NULL, 0, NULL, NULL, |
|
|
|
0, NULL, 0, NULL, NULL, |
|
|
@ -213,16 +209,16 @@ bool UBQuickTimeFile::beginSession() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Audio is mono, and compressed to AAC at 128kbps |
|
|
|
// Audio is mono, and compressed to AAC at 128kbps |
|
|
|
|
|
|
|
|
|
|
|
AudioChannelLayout audioChannelLayout = { |
|
|
|
AudioChannelLayout audioChannelLayout = { |
|
|
|
.mChannelLayoutTag = kAudioChannelLayoutTag_Mono, |
|
|
|
.mChannelLayoutTag = kAudioChannelLayoutTag_Mono, |
|
|
|
.mChannelBitmap = 0, |
|
|
|
.mChannelBitmap = 0, |
|
|
|
.mNumberChannelDescriptions = 0 |
|
|
|
.mNumberChannelDescriptions = 0 |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
NSData *channelLayoutAsData = [NSData dataWithBytes:&audioChannelLayout |
|
|
|
NSData *channelLayoutAsData = [NSData dataWithBytes:&audioChannelLayout |
|
|
|
length:offsetof(AudioChannelLayout, mChannelDescriptions)]; |
|
|
|
length:offsetof(AudioChannelLayout, mChannelDescriptions)]; |
|
|
|
|
|
|
|
|
|
|
|
NSDictionary * compressionAudioSettings = @{ |
|
|
|
NSDictionary * compressionAudioSettings = @{ |
|
|
|
AVFormatIDKey : [NSNumber numberWithUnsignedInt:kAudioFormatMPEG4AAC], |
|
|
|
AVFormatIDKey : [NSNumber numberWithUnsignedInt:kAudioFormatMPEG4AAC], |
|
|
|
AVEncoderBitRateKey : [NSNumber numberWithInteger:128000], |
|
|
|
AVEncoderBitRateKey : [NSNumber numberWithInteger:128000], |
|
|
@ -231,12 +227,12 @@ bool UBQuickTimeFile::beginSession() |
|
|
|
AVNumberOfChannelsKey : [NSNumber numberWithUnsignedInteger:1] |
|
|
|
AVNumberOfChannelsKey : [NSNumber numberWithUnsignedInteger:1] |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
mAudioWriterInput = [[AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio |
|
|
|
mAudioWriterInput = [[AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio |
|
|
|
outputSettings:compressionAudioSettings] retain]; |
|
|
|
outputSettings:compressionAudioSettings] retain]; |
|
|
|
|
|
|
|
|
|
|
|
NSCParameterAssert([mVideoWriter canAddInput:mAudioWriterInput]); |
|
|
|
NSCParameterAssert([mVideoWriter canAddInput:mAudioWriterInput]); |
|
|
|
[mVideoWriter addInput:mAudioWriterInput]; |
|
|
|
[mVideoWriter addInput:mAudioWriterInput]; |
|
|
|
|
|
|
|
|
|
|
|
qDebug() << "audio writer input created and added"; |
|
|
|
qDebug() << "audio writer input created and added"; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -247,7 +243,7 @@ bool UBQuickTimeFile::beginSession() |
|
|
|
|
|
|
|
|
|
|
|
mStartTime = CFAbsoluteTimeGetCurrent(); // used for audio timestamp calculation |
|
|
|
mStartTime = CFAbsoluteTimeGetCurrent(); // used for audio timestamp calculation |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (mVideoWriter != nil) && (mVideoWriterInput != nil) && canStartWriting; |
|
|
|
return (mVideoWriter != nil) && (mVideoWriterInput != nil) && canStartWriting; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -257,7 +253,7 @@ bool UBQuickTimeFile::beginSession() |
|
|
|
void UBQuickTimeFile::endSession() |
|
|
|
void UBQuickTimeFile::endSession() |
|
|
|
{ |
|
|
|
{ |
|
|
|
[mVideoWriterInput markAsFinished]; |
|
|
|
[mVideoWriterInput markAsFinished]; |
|
|
|
bool success = [mVideoWriter finishWriting]; |
|
|
|
[mVideoWriter finishWritingWithCompletionHandler:^{}]; |
|
|
|
|
|
|
|
|
|
|
|
[mAdaptor release]; |
|
|
|
[mAdaptor release]; |
|
|
|
[mVideoWriterInput release]; |
|
|
|
[mVideoWriterInput release]; |
|
|
@ -309,12 +305,12 @@ CVPixelBufferRef UBQuickTimeFile::newPixelBuffer() |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
void UBQuickTimeFile::appendVideoFrame(CVPixelBufferRef pixelBuffer, long msTimeStamp) |
|
|
|
void UBQuickTimeFile::appendVideoFrame(CVPixelBufferRef pixelBuffer, long msTimeStamp) |
|
|
|
{ |
|
|
|
{ |
|
|
|
CMTime t = CMTimeMake((msTimeStamp * mTimeScale / 1000.0), mTimeScale); |
|
|
|
CMTime t = CMTimeMake((msTimeStamp * mTimeScale / 1000.0), mTimeScale); |
|
|
|
|
|
|
|
|
|
|
|
bool added = [mAdaptor appendPixelBuffer: pixelBuffer |
|
|
|
bool added = [mAdaptor appendPixelBuffer: pixelBuffer |
|
|
|
withPresentationTime: t]; |
|
|
|
withPresentationTime: t]; |
|
|
|
|
|
|
|
|
|
|
|
if (!added) |
|
|
|
if (!added) |
|
|
|
setLastErrorMessage(QString("Could not encode frame at time %1").arg(msTimeStamp)); |
|
|
|
setLastErrorMessage(QString("Could not encode frame at time %1").arg(msTimeStamp)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -333,10 +329,10 @@ void UBQuickTimeFile::appendVideoFrame(CVPixelBufferRef pixelBuffer, long msTime |
|
|
|
* (implemented in the UBAudioQueueRecorder class) and the recording, handled |
|
|
|
* (implemented in the UBAudioQueueRecorder class) and the recording, handled |
|
|
|
* by the AVAssetWriterInput instance mAudioWriterInput. |
|
|
|
* by the AVAssetWriterInput instance mAudioWriterInput. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
void UBQuickTimeFile::appendAudioBuffer(void* pBuffer, |
|
|
|
void UBQuickTimeFile::appendAudioBuffer(void* pBuffer, |
|
|
|
long pLength) |
|
|
|
long pLength) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if(!mRecordAudio) |
|
|
|
if(!mRecordAudio) |
|
|
|
return; |
|
|
|
return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -368,24 +364,34 @@ void UBQuickTimeFile::appendAudioBuffer(void* pBuffer, |
|
|
|
true, |
|
|
|
true, |
|
|
|
NULL, |
|
|
|
NULL, |
|
|
|
NULL, |
|
|
|
NULL, |
|
|
|
mAudioFormatDescription, |
|
|
|
mAudioFormatDescription, |
|
|
|
nSamples, |
|
|
|
nSamples, |
|
|
|
timeStamp, |
|
|
|
timeStamp, |
|
|
|
NULL, |
|
|
|
NULL, |
|
|
|
&sampleBuffer); |
|
|
|
&sampleBuffer); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add the audio sample to the asset writer input |
|
|
|
// Wait until the AssetWriterInput is ready, but no more than 100ms |
|
|
|
if ([mAudioWriterInput isReadyForMoreMediaData]) |
|
|
|
// (bit of a duct tape solution; cleaner solution would be to use a QQueue, |
|
|
|
|
|
|
|
// similar to the VideoWriter) |
|
|
|
|
|
|
|
int waitTime = 0; |
|
|
|
|
|
|
|
while(![mAudioWriterInput isReadyForMoreMediaData] && waitTime < 100) { |
|
|
|
|
|
|
|
waitTime += 10; |
|
|
|
|
|
|
|
usleep(10000); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ([mAudioWriterInput isReadyForMoreMediaData]) { |
|
|
|
if(![mAudioWriterInput appendSampleBuffer:sampleBuffer]) |
|
|
|
if(![mAudioWriterInput appendSampleBuffer:sampleBuffer]) |
|
|
|
setLastErrorMessage(QString("Failed to append sample buffer to audio input")); |
|
|
|
setLastErrorMessage(QString("Failed to append sample buffer to audio input")); |
|
|
|
|
|
|
|
} |
|
|
|
else |
|
|
|
else |
|
|
|
setLastErrorMessage(QString("Audio Writer not ready; sample dropped")); |
|
|
|
setLastErrorMessage(QString("AudioWriterInput not ready. Buffer dropped.")); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CFRelease(sampleBuffer); |
|
|
|
CFRelease(sampleBuffer); |
|
|
|
CFRelease(blockBuffer); |
|
|
|
CFRelease(blockBuffer); |
|
|
|
|
|
|
|
|
|
|
|
// The audioQueueBuffers are all freed when UBAudioQueueRecorder::close() is called |
|
|
|
// The audioQueueBuffers are all freed when UBAudioQueueRecorder::close() is called |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|