diff --git a/OpenBoard.pro b/OpenBoard.pro
index 4ba4b151..b6802c50 100644
--- a/OpenBoard.pro
+++ b/OpenBoard.pro
@@ -130,13 +130,13 @@ macx {
LIBS += -framework Foundation
LIBS += -framework Cocoa
LIBS += -framework Carbon
+ LIBS += -framework AVFoundation
+ LIBS += -framework CoreMedia
LIBS += -lcrypto
CONFIG(release, debug|release):CONFIG += x86_64
CONFIG(debug, debug|release):CONFIG += x86_64
- # TODO Craig: switch to 64bit
-
QMAKE_MAC_SDK = macosx
QMAKE_MACOSX_DEPLOYMENT_TARGET = "10.10"
diff --git a/src/podcast/UBPodcastController.cpp b/src/podcast/UBPodcastController.cpp
index 45a5e435..12641434 100644
--- a/src/podcast/UBPodcastController.cpp
+++ b/src/podcast/UBPodcastController.cpp
@@ -59,9 +59,9 @@
#ifdef Q_OS_WIN
#include "windowsmedia/UBWindowsMediaVideoEncoder.h"
#include "windowsmedia/UBWaveRecorder.h"
-//#elif defined(Q_OS_OSX)
-// #include "quicktime/UBQuickTimeVideoEncoder.h"
-// #include "quicktime/UBAudioQueueRecorder.h"
+#elif defined(Q_OS_OSX)
+ #include "quicktime/UBQuickTimeVideoEncoder.h"
+ #include "quicktime/UBAudioQueueRecorder.h"
#endif
#include "core/memcheck.h"
@@ -305,8 +305,8 @@ void UBPodcastController::start()
#ifdef Q_OS_WIN
mVideoEncoder = new UBWindowsMediaVideoEncoder(this); //deleted on stop
-//#elif defined(Q_OS_OSX)
-// mVideoEncoder = new UBQuickTimeVideoEncoder(this); //deleted on stop
+#elif defined(Q_OS_OSX)
+ mVideoEncoder = new UBQuickTimeVideoEncoder(this); //deleted on stop
#endif
if (mVideoEncoder)
@@ -795,8 +795,8 @@ QStringList UBPodcastController::audioRecordingDevices()
#ifdef Q_OS_WIN
devices = UBWaveRecorder::waveInDevices();
-//#elif defined(Q_OS_OSX)
-// devices = UBAudioQueueRecorder::waveInDevices();
+#elif defined(Q_OS_OSX)
+ devices = UBAudioQueueRecorder::waveInDevices();
#endif
return devices;
diff --git a/src/podcast/podcast.pri b/src/podcast/podcast.pri
index d6d5aa94..3dde6273 100644
--- a/src/podcast/podcast.pri
+++ b/src/podcast/podcast.pri
@@ -1,34 +1,35 @@
-
-HEADERS += src/podcast/UBPodcastController.h \
- src/podcast/UBAbstractVideoEncoder.h \
- src/podcast/UBPodcastRecordingPalette.h \
- src/podcast/youtube/UBYouTubePublisher.h \
- src/podcast/intranet/UBIntranetPodcastPublisher.h
-
-SOURCES += src/podcast/UBPodcastController.cpp \
- src/podcast/UBAbstractVideoEncoder.cpp \
- src/podcast/UBPodcastRecordingPalette.cpp \
- src/podcast/youtube/UBYouTubePublisher.cpp \
- src/podcast/intranet/UBIntranetPodcastPublisher.cpp
-
-win32 {
-
- SOURCES += src/podcast/windowsmedia/UBWindowsMediaVideoEncoder.cpp \
- src/podcast/windowsmedia/UBWindowsMediaFile.cpp \
- src/podcast/windowsmedia/UBWaveRecorder.cpp
-
- HEADERS += src/podcast/windowsmedia/UBWindowsMediaVideoEncoder.h \
- src/podcast/windowsmedia/UBWindowsMediaFile.h \
- src/podcast/windowsmedia/UBWaveRecorder.h
-}
-
-#macx {
-
-# SOURCES += src/podcast/quicktime/UBQuickTimeVideoEncoder.cpp \
-# src/podcast/quicktime/UBQuickTimeFile.cpp \
-# src/podcast/quicktime/UBAudioQueueRecorder.cpp
-
-# HEADERS += src/podcast/quicktime/UBQuickTimeVideoEncoder.h \
-# src/podcast/quicktime/UBQuickTimeFile.h \
-# src/podcast/quicktime/UBAudioQueueRecorder.h
-#}
+
+HEADERS += src/podcast/UBPodcastController.h \
+ src/podcast/UBAbstractVideoEncoder.h \
+ src/podcast/UBPodcastRecordingPalette.h \
+ src/podcast/youtube/UBYouTubePublisher.h \
+ src/podcast/intranet/UBIntranetPodcastPublisher.h
+
+SOURCES += src/podcast/UBPodcastController.cpp \
+ src/podcast/UBAbstractVideoEncoder.cpp \
+ src/podcast/UBPodcastRecordingPalette.cpp \
+ src/podcast/youtube/UBYouTubePublisher.cpp \
+ src/podcast/intranet/UBIntranetPodcastPublisher.cpp
+
+win32 {
+
+ SOURCES += src/podcast/windowsmedia/UBWindowsMediaVideoEncoder.cpp \
+ src/podcast/windowsmedia/UBWindowsMediaFile.cpp \
+ src/podcast/windowsmedia/UBWaveRecorder.cpp
+
+ HEADERS += src/podcast/windowsmedia/UBWindowsMediaVideoEncoder.h \
+ src/podcast/windowsmedia/UBWindowsMediaFile.h \
+ src/podcast/windowsmedia/UBWaveRecorder.h
+}
+
+macx {
+
+ SOURCES += src/podcast/quicktime/UBQuickTimeVideoEncoder.cpp \
+ src/podcast/quicktime/UBAudioQueueRecorder.cpp
+
+ HEADERS += src/podcast/quicktime/UBQuickTimeVideoEncoder.h \
+ src/podcast/quicktime/UBQuickTimeFile.h \
+ src/podcast/quicktime/UBAudioQueueRecorder.h
+
+ OBJECTIVE_SOURCES += src/podcast/quicktime/UBQuickTimeFile.mm
+}
diff --git a/src/podcast/quicktime/UBAudioQueueRecorder.cpp b/src/podcast/quicktime/UBAudioQueueRecorder.cpp
index 1c529ab5..43c106f2 100644
--- a/src/podcast/quicktime/UBAudioQueueRecorder.cpp
+++ b/src/podcast/quicktime/UBAudioQueueRecorder.cpp
@@ -151,10 +151,10 @@ QString UBAudioQueueRecorder::deviceUIDFromDeviceID(AudioDeviceID id)
{
char *cname = new char[1024];
- CFStringGetCString (name, cname, 1024, kCFStringEncodingASCII);
+ CFStringGetCString (name, cname, 1024, kCFStringEncodingISOLatin1);
int length = CFStringGetLength (name);
- uid = QString::fromAscii(cname, length);
+ uid = QString::fromLatin1(cname, length);
delete cname;
diff --git a/src/podcast/quicktime/UBQuickTimeFile.cpp b/src/podcast/quicktime/UBQuickTimeFile.cpp
deleted file mode 100644
index 97ebf048..00000000
--- a/src/podcast/quicktime/UBQuickTimeFile.cpp
+++ /dev/null
@@ -1,656 +0,0 @@
-/*
- * Copyright (C) 2013 Open Education Foundation
- *
- * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour
- * l'Education Numérique en Afrique (GIP ENA)
- *
- * This file is part of OpenBoard.
- *
- * OpenBoard is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3 of the License,
- * with a specific linking exception for the OpenSSL project's
- * "OpenSSL" library (or with modified versions of it that use the
- * same license as the "OpenSSL" library).
- *
- * OpenBoard is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with OpenBoard. If not, see .
- */
-
-
-
-
-#include "UBQuickTimeFile.h"
-
-#include
-
-#include "UBAudioQueueRecorder.h"
-#include
-
-#include "core/memcheck.h"
-
-QQueue UBQuickTimeFile::frameQueue;
-QMutex UBQuickTimeFile::frameQueueMutex;
-QWaitCondition UBQuickTimeFile::frameBufferNotEmpty;
-
-
-UBQuickTimeFile::UBQuickTimeFile(QObject * pParent)
- : QThread(pParent)
- , mVideoCompressionSession(0)
- , mVideoMedia(0)
- , mSoundMedia(0)
- , mVideoOutputTrack(0)
- , mSoundOutputTrack(0)
- , mCVPixelBufferPool(0)
- , mOutputMovie(0)
- , mFramesPerSecond(-1)
- , mTimeScale(100)
- , mRecordAudio(true)
- , mWaveRecorder(0)
- , mSouldStopCompression(false)
- , mCompressionSessionRunning(false)
- , mPendingFrames(0)
-{
- // NOOP
-}
-
-
-bool UBQuickTimeFile::init(const QString& pVideoFileName, const QString& pProfileData, int pFramesPerSecond
- , const QSize& pFrameSize, bool pRecordAudio, const QString& audioRecordingDevice)
-{
- mFrameSize = pFrameSize;
- mFramesPerSecond = pFramesPerSecond;
- mVideoFileName = pVideoFileName;
- mRecordAudio = pRecordAudio && QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5; // Audio Queue are available in 10.5 +;
-
- if (mRecordAudio)
- mAudioRecordingDeviceName = audioRecordingDevice;
- else
- mAudioRecordingDeviceName = "";
-
- if (pProfileData.toLower() == "lossless")
- mSpatialQuality = codecLosslessQuality;
- if (pProfileData.toLower() == "high")
- mSpatialQuality = codecHighQuality;
- else if (pProfileData.toLower() == "normal")
- mSpatialQuality = codecNormalQuality;
- else if (pProfileData.toLower() == "low")
- mSpatialQuality = codecLowQuality;
- else
- mSpatialQuality = codecHighQuality;
-
- qDebug() << "Quality " << pProfileData << mSpatialQuality;
-
- return true;
-
-}
-
-
-void UBQuickTimeFile::run()
-{
- EnterMoviesOnThread(kCSAcceptThreadSafeComponentsOnlyMode);
-
- mSouldStopCompression = false;
- mPendingFrames = 0;
-
- createCompressionSession();
-
- mCompressionSessionRunning = true;
- emit compressionSessionStarted();
-
- while(!mSouldStopCompression)
- {
- frameQueueMutex.lock();
- //qDebug() << "run .... wait" << QTime::currentTime();
-
- frameBufferNotEmpty.wait(&UBQuickTimeFile::frameQueueMutex);
-
- //qDebug() << "awakend ..." << QTime::currentTime();
- if (!frameQueue.isEmpty())
- {
- QQueue localQueue = frameQueue;
- frameQueue.clear();
-
- frameQueueMutex.unlock();
-
- while (!localQueue.isEmpty())
- {
- VideoFrame frame = localQueue.dequeue();
- appendVideoFrame(frame.buffer, frame.timestamp);
- }
- }
- else
- {
- frameQueueMutex.unlock();
- }
- }
-
- flushPendingFrames();
-}
-
-
-bool UBQuickTimeFile::createCompressionSession()
-{
- CodecType codecType = kH264CodecType;
-
- CFStringRef keys[] = {kCVPixelBufferPixelFormatTypeKey, kCVPixelBufferWidthKey, kCVPixelBufferHeightKey};
-
- int width = mFrameSize.width();
- int height = mFrameSize.height();
- int pixelFormat = k32BGRAPixelFormat;
-
- CFTypeRef values[] =
- {
- (CFTypeRef)CFNumberCreate(0, kCFNumberIntType, (void*)&pixelFormat),
- (CFTypeRef)CFNumberCreate(0, kCFNumberIntType, (void*)&width),
- (CFTypeRef)CFNumberCreate(0, kCFNumberIntType, (void*)&height)
- };
-
- CFDictionaryRef pixelBufferAttributes = CFDictionaryCreate(kCFAllocatorDefault
- , (const void **)keys, (const void **)values, 3, 0, 0);
-
- if(!pixelBufferAttributes)
- {
- setLastErrorMessage("Could not create CV buffer pool pixel buffer attributes");
- return false;
- }
-
- OSStatus err = noErr;
- ICMEncodedFrameOutputRecord encodedFrameOutputRecord = {NULL, NULL, NULL};
- ICMCompressionSessionOptionsRef sessionOptions = 0;
-
- err = ICMCompressionSessionOptionsCreate(0, &sessionOptions);
- if(err)
- {
- setLastErrorMessage(QString("ICMCompressionSessionOptionsCreate() failed %1").arg(err));
- goto bail;
- }
-
- // We must set this flag to enable P or B frames.
- err = ICMCompressionSessionOptionsSetAllowTemporalCompression(sessionOptions, true);
- if(err)
- {
- setLastErrorMessage(QString("ICMCompressionSessionOptionsSetAllowTemporalCompression() failed %1").arg(err));
- goto bail;
- }
-
- // We must set this flag to enable B frames.
- err = ICMCompressionSessionOptionsSetAllowFrameReordering(sessionOptions, true);
- if(err)
- {
- setLastErrorMessage(QString("ICMCompressionSessionOptionsSetAllowFrameReordering() failed %1").arg(err));
- goto bail;
- }
-
- // Set the maximum key frame interval, also known as the key frame rate.
- err = ICMCompressionSessionOptionsSetMaxKeyFrameInterval(sessionOptions, mFramesPerSecond);
- if(err)
- {
- setLastErrorMessage(QString("ICMCompressionSessionOptionsSetMaxKeyFrameInterval() failed %1").arg(err));
- goto bail;
- }
-
- // This allows the compressor more flexibility (ie, dropping and coalescing frames).
- err = ICMCompressionSessionOptionsSetAllowFrameTimeChanges(sessionOptions, true);
- if(err)
- {
- setLastErrorMessage(QString("ICMCompressionSessionOptionsSetAllowFrameTimeChanges() failed %1").arg(err));
- goto bail;
- }
-
- // Set the average quality.
- err = ICMCompressionSessionOptionsSetProperty(sessionOptions,
- kQTPropertyClass_ICMCompressionSessionOptions,
- kICMCompressionSessionOptionsPropertyID_Quality,
- sizeof(mSpatialQuality),
- &mSpatialQuality);
- if(err)
- {
- setLastErrorMessage(QString("ICMCompressionSessionOptionsSetProperty(Quality) failed %1").arg(err));
- goto bail;
- }
-
- //qDebug() << "available quality" << mSpatialQuality;
-
- encodedFrameOutputRecord.encodedFrameOutputCallback = addEncodedFrameToMovie;
- encodedFrameOutputRecord.encodedFrameOutputRefCon = this;
- encodedFrameOutputRecord.frameDataAllocator = 0;
-
- err = ICMCompressionSessionCreate(0, mFrameSize.width(), mFrameSize.height(), codecType, mTimeScale,
- sessionOptions, pixelBufferAttributes, &encodedFrameOutputRecord, &mVideoCompressionSession);
- if(err)
- {
- setLastErrorMessage(QString("ICMCompressionSessionCreate() failed %1").arg(err));
- goto bail;
- }
-
- mCVPixelBufferPool = ICMCompressionSessionGetPixelBufferPool(mVideoCompressionSession);
-
- if(!mCVPixelBufferPool)
- {
- setLastErrorMessage("ICMCompressionSessionGetPixelBufferPool() failed.");
- err = !noErr;
- goto bail;
- }
-
- if(mRecordAudio)
- {
- mWaveRecorder = new UBAudioQueueRecorder();
-
- if(mWaveRecorder->init(mAudioRecordingDeviceName))
- {
- connect(mWaveRecorder, SIGNAL(newWaveBuffer(void*, long, int , const AudioStreamPacketDescription*))
- , this, SLOT(appendAudioBuffer(void*, long, int, const AudioStreamPacketDescription*)));
-
- connect(mWaveRecorder, SIGNAL(audioLevelChanged(quint8)), this, SIGNAL(audioLevelChanged(quint8)));
- }
- else
- {
- setLastErrorMessage(mWaveRecorder->lastErrorMessage());
- mWaveRecorder->deleteLater();
- }
- }
-
- createMovie();
-
-bail:
- ICMCompressionSessionOptionsRelease(sessionOptions);
- sessionOptions = 0;
-
- CFRelease(pixelBufferAttributes);
-
- return err == noErr;
-}
-
-
-void UBQuickTimeFile::stop()
-{
- mSouldStopCompression = true;
-}
-
-bool UBQuickTimeFile::flushPendingFrames()
-{
- mCompressionSessionRunning = false;
-
- if (mWaveRecorder)
- {
- mWaveRecorder->close();
- mWaveRecorder->deleteLater();
- }
-
- //Flush pending frames in compression session
- OSStatus err = ICMCompressionSessionCompleteFrames(mVideoCompressionSession, true, 0, 0);
- if (err)
- {
- setLastErrorMessage(QString("ICMCompressionSessionCompleteFrames() failed %1").arg(err));
- return false;
- }
-
- return true;
-}
-
-
-bool UBQuickTimeFile::closeCompressionSession()
-{
- OSStatus err = noErr;
-
- if (mVideoMedia)
- {
- // End the media sample-adding session.
- err = EndMediaEdits(mVideoMedia);
- if (err)
- {
- setLastErrorMessage(QString("EndMediaEdits(mVideoMedia) failed %1").arg(err));
- return false;
- }
-
- // Make sure things are extra neat.
- ExtendMediaDecodeDurationToDisplayEndTime(mVideoMedia, 0);
-
- // Insert the stuff we added into the track, at the end.
- Track videoTrack = GetMediaTrack(mVideoMedia);
-
- err = InsertMediaIntoTrack(videoTrack,
- GetTrackDuration(videoTrack),
- 0, GetMediaDisplayDuration(mVideoMedia),
- fixed1);
- mVideoMedia = 0;
-
- if (err)
- {
- setLastErrorMessage(QString("InsertMediaIntoTrack() failed %1").arg(err));
- return false;
- }
-
- if (mSoundMedia)
- {
- err = EndMediaEdits(mSoundMedia);
- if(err)
- {
- setLastErrorMessage(QString("EndMediaEdits(mAudioMedia) failed %1").arg(err));
- return false;
- }
-
- Track soundTrack = GetMediaTrack(mSoundMedia);
-
- err = InsertMediaIntoTrack(soundTrack,
- GetTrackDuration(soundTrack),
- 0, GetMediaDisplayDuration(mSoundMedia),
- fixed1);
-
- mSoundMedia = 0;
-
- if (err)
- {
- setLastErrorMessage(QString("InsertMediaIntoTrack(mAudioMedia) failed %1").arg(err));
- }
-
- TimeValue soundTrackDuration = GetTrackDuration(soundTrack);
- TimeValue videoTrackDuration = GetTrackDuration(videoTrack);
-
- if (soundTrackDuration > videoTrackDuration)
- {
- qDebug() << "Sound track is longer then video track" << soundTrackDuration << ">" << videoTrackDuration;
- DeleteTrackSegment(soundTrack, videoTrackDuration, soundTrackDuration - videoTrackDuration);
- }
-
- DisposeHandle((Handle)mSoundDescription);
- }
- }
-
- // Write the movie header to the file.
- err = AddMovieToStorage(mOutputMovie, mOutputMovieDataHandler);
- if (err)
- {
- setLastErrorMessage(QString("AddMovieToStorage() failed %1").arg(err));
- return false;
- }
-
- err = UpdateMovieInStorage(mOutputMovie, mOutputMovieDataHandler);
- if (err)
- {
- setLastErrorMessage(QString("UpdateMovieInStorage() failed %1").arg(err));
- return false;
- }
-
- err = CloseMovieStorage(mOutputMovieDataHandler);
- if (err)
- {
- setLastErrorMessage(QString("CloseMovieStorage() failed %1").arg(err));
- return false;
- }
-
- CVPixelBufferPoolRelease(mCVPixelBufferPool);
- mCVPixelBufferPool = 0;
-
- mOutputMovie = 0;
- mOutputMovieDataHandler = 0;
- mVideoCompressionSession = 0;
-
- ExitMoviesOnThread();
-
- return true;
-}
-
-
-OSStatus UBQuickTimeFile::addEncodedFrameToMovie(void *encodedFrameOutputRefCon,
- ICMCompressionSessionRef session,
- OSStatus err,
- ICMEncodedFrameRef encodedFrame,
- void *reserved)
-{
- Q_UNUSED(session);
- Q_UNUSED(reserved);
-
- UBQuickTimeFile *quickTimeFile = (UBQuickTimeFile *)encodedFrameOutputRefCon;
-
- if(quickTimeFile)
- quickTimeFile->addEncodedFrame(encodedFrame, err);
-
- return noErr;
-}
-
-
-void UBQuickTimeFile::addEncodedFrame(ICMEncodedFrameRef encodedFrame, OSStatus frameErr)
-{
- mPendingFrames--;
-
- //qDebug() << "addEncodedFrame" << mSouldStopCompression << mPendingFrames;
-
- if(frameErr == noErr)
- {
- if (mVideoMedia)
- {
- OSStatus err = AddMediaSampleFromEncodedFrame(mVideoMedia, encodedFrame, 0);
-
- if(err)
- {
- setLastErrorMessage(QString("AddMediaSampleFromEncodedFrame() failed %1").arg(err));
- }
- }
- }
- else
- {
- setLastErrorMessage(QString("addEncodedFrame received an error %1").arg(frameErr));
- }
-
- if (mSouldStopCompression && mPendingFrames == 0)
- {
- closeCompressionSession();
- }
-}
-
-
-bool UBQuickTimeFile::createMovie()
-{
- if(!mOutputMovie)
- {
- OSStatus err = noErr;
-
- Handle dataRef;
- OSType dataRefType;
-
- CFStringRef filePath = CFStringCreateWithCString(0, mVideoFileName.toUtf8().constData(), kCFStringEncodingUTF8);
-
- QTNewDataReferenceFromFullPathCFString(filePath, kQTPOSIXPathStyle, 0, &dataRef, &dataRefType);
-
- err = CreateMovieStorage(dataRef, dataRefType, 'TVOD', 0, createMovieFileDeleteCurFile, &mOutputMovieDataHandler, &mOutputMovie);
- if(err)
- {
- setLastErrorMessage(QString("CreateMovieStorage() failed %1").arg(err));
- return false;
- }
-
- mVideoOutputTrack = NewMovieTrack(mOutputMovie, X2Fix(mFrameSize.width()), X2Fix(mFrameSize.height()), 0);
- err = GetMoviesError();
-
- if( err )
- {
- setLastErrorMessage(QString("NewMovieTrack(Video) failed %1").arg(err));
- return false;
- }
-
- if(!createVideoMedia())
- return false;
-
- if(mRecordAudio)
- {
- mSoundOutputTrack = NewMovieTrack(mOutputMovie, 0, 0, kFullVolume);
- err = GetMoviesError();
-
- if(err)
- {
- setLastErrorMessage(QString("NewMovieTrack(Sound) failed %1").arg(err));
- return false;
- }
-
- if(!createAudioMedia())
- return false;
- }
- }
-
- return true;
-}
-
-
-bool UBQuickTimeFile::createVideoMedia()
-{
- mVideoMedia = NewTrackMedia(mVideoOutputTrack, VideoMediaType, mTimeScale, 0, 0);
- OSStatus err = GetMoviesError();
-
- if (err)
- {
- setLastErrorMessage(QString("NewTrackMedia(VideoMediaType) failed %1").arg(err));
- return false;
- }
-
- err = BeginMediaEdits(mVideoMedia);
- if (err)
- {
- setLastErrorMessage(QString("BeginMediaEdits(VideoMediaType) failed %1").arg(err));
- return false;
- }
-
- return true;
-}
-
-
-bool UBQuickTimeFile::createAudioMedia()
-{
- if(mRecordAudio)
- {
- mAudioDataFormat = UBAudioQueueRecorder::audioFormat();
-
- mSoundMedia = NewTrackMedia(mSoundOutputTrack, SoundMediaType, mAudioDataFormat.mSampleRate, 0, 0);
- OSStatus err = GetMoviesError();
- if(err)
- {
- setLastErrorMessage(QString("NewTrackMedia(AudioMediaType) failed %1").arg(err));
- return false;
- }
-
- err = BeginMediaEdits(mSoundMedia);
- if(err)
- {
- setLastErrorMessage(QString("BeginMediaEdits(AudioMediaType) failed %1").arg(err));
- return false;
- }
-
- err = QTSoundDescriptionCreate(&mAudioDataFormat, 0, 0, 0, 0,
- kQTSoundDescriptionKind_Movie_LowestPossibleVersion,
- &mSoundDescription);
- if (err)
- {
- setLastErrorMessage(QString("QTSoundDescriptionCreate() failed %1").arg(err));
- return false;
- }
-
-
- err = QTSoundDescriptionGetProperty(mSoundDescription, kQTPropertyClass_SoundDescription,
- kQTSoundDescriptionPropertyID_AudioStreamBasicDescription,
- sizeof(mAudioDataFormat), &mAudioDataFormat, 0);
- if (err)
- {
- setLastErrorMessage(QString("QTSoundDescriptionGetProperty() failed %1").arg(err));
- return false;
- }
-
- }
-
- return true;
-}
-
-
-UBQuickTimeFile::~UBQuickTimeFile()
-{
- // NOOP
-}
-
-
-CVPixelBufferRef UBQuickTimeFile::newPixelBuffer()
-{
- CVPixelBufferRef pixelBuffer = 0;
-
- if(CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, mCVPixelBufferPool, &pixelBuffer) != kCVReturnSuccess)
- {
- setLastErrorMessage("Could not retreive CV buffer from pool");
- return 0;
- }
-
- return pixelBuffer;
-}
-
-
-void UBQuickTimeFile::appendVideoFrame(CVPixelBufferRef pixelBuffer, long msTimeStamp)
-{
- TimeValue64 msTimeStampScaled = msTimeStamp * mTimeScale / 1000;
-
- /*
- {
- CVPixelBufferLockBaseAddress(pixelBuffer, 0) ;
- void *pixelBufferAddress = CVPixelBufferGetBaseAddress(pixelBuffer);
- qDebug() << "will comp newVideoFrame - PixelBuffer @" << pixelBufferAddress
- << QTime::currentTime().toString("ss:zzz") << QThread::currentThread();
- CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
- }
- */
-
- OSStatus err = ICMCompressionSessionEncodeFrame(mVideoCompressionSession, pixelBuffer,
- msTimeStampScaled, 0, kICMValidTime_DisplayTimeStampIsValid,
- 0, 0, 0);
-
- if (err == noErr)
- {
- mPendingFrames++;
- }
- else
- {
- setLastErrorMessage(QString("Could not encode frame %1").arg(err));
- }
-
- CVPixelBufferRelease(pixelBuffer);
-}
-
-
-void UBQuickTimeFile::appendAudioBuffer(void* pBuffer, long pLength, int inNumberPacketDescriptions, const AudioStreamPacketDescription* inPacketDescs)
-{
- Q_UNUSED(pLength);
- //qDebug() << "appendAudioBuffer" << QThread::currentThread();
-
- if(mRecordAudio)
- {
- for (int i = 0; i < inNumberPacketDescriptions; i++)
- {
- OSStatus err = AddMediaSample2(mSoundMedia,
- (UInt8*)pBuffer + inPacketDescs[i].mStartOffset,
- inPacketDescs[i].mDataByteSize,
- mAudioDataFormat.mFramesPerPacket,
- 0,
- (SampleDescriptionHandle)mSoundDescription,
- 1,
- 0,
- 0);
- if (err)
- {
- setLastErrorMessage(QString("AddMediaSample2(soundMedia) failed %1").arg(err));
- }
- }
- }
-#ifdef Q_OS_OSX
- free((void*)inPacketDescs);
-#endif
-}
-
-
-void UBQuickTimeFile::setLastErrorMessage(const QString& error)
-{
- mLastErrorMessage = error;
- qWarning() << "UBQuickTimeFile error" << error;
-}
-
-
diff --git a/src/podcast/quicktime/UBQuickTimeFile.h b/src/podcast/quicktime/UBQuickTimeFile.h
index d6fe2ce6..1d1efd70 100644
--- a/src/podcast/quicktime/UBQuickTimeFile.h
+++ b/src/podcast/quicktime/UBQuickTimeFile.h
@@ -30,12 +30,29 @@
#include
-#include
-#include
-#include
+#include
#include "UBAudioQueueRecorder.h"
+
+
+// Trick to get around the fact that the C++ compiler doesn't
+// like Objective C code.
+
+#ifdef __OBJC__ // defined by the Objective C compiler
+ @class AVAssetWriter;
+ @class AVAssetWriterInput;
+ @class AVAssetWriterInputPixelBufferAdaptor;
+
+ typedef AVAssetWriter* AssetWriterPTR;
+ typedef AVAssetWriterInput* AssetWriterInputPTR;
+ typedef AVAssetWriterInputPixelBufferAdaptor* AssetWriterInputAdaptorPTR;
+#else
+ typedef void* AssetWriterPTR;
+ typedef void* AssetWriterInputPTR;
+ typedef void* AssetWriterInputAdaptorPTR;
+#endif
+
class UBQuickTimeFile : public QThread
{
Q_OBJECT;
@@ -52,15 +69,11 @@ class UBQuickTimeFile : public QThread
CVPixelBufferRef newPixelBuffer();
- bool isCompressionSessionRunning()
- {
- return mCompressionSessionRunning;
- }
+ bool isCompressionSessionRunning() { return mCompressionSessionRunning; }
- QString lastErrorMessage() const
- {
- return mLastErrorMessage;
- }
+ QString lastErrorMessage() const { return mLastErrorMessage; }
+
+ void endSession();
struct VideoFrame
{
@@ -79,47 +92,19 @@ class UBQuickTimeFile : public QThread
protected:
void run();
- private slots:
-
- void appendAudioBuffer(void* pBuffer, long pLength, int inNumberPacketDescriptions
- , const AudioStreamPacketDescription* inPacketDescs);
private:
- static OSStatus addEncodedFrameToMovie(void *encodedFrameOutputRefCon,
- ICMCompressionSessionRef session,
- OSStatus err,
- ICMEncodedFrameRef encodedFrame,
- void *reserved);
+ bool beginSession();
void appendVideoFrame(CVPixelBufferRef pixelBuffer, long msTimeStamp);
- void addEncodedFrame(ICMEncodedFrameRef encodedFrame, OSStatus err);
-
- bool createCompressionSession();
- bool closeCompressionSession();
- bool createMovie();
-
- bool createVideoMedia();
- bool createAudioMedia();
-
void setLastErrorMessage(const QString& error);
bool flushPendingFrames();
- ICMCompressionSessionRef mVideoCompressionSession;
-
- Media mVideoMedia;
- Media mSoundMedia;
- Track mVideoOutputTrack;
- Track mSoundOutputTrack;
-
volatile CVPixelBufferPoolRef mCVPixelBufferPool;
- SoundDescriptionHandle mSoundDescription;
-
- Movie mOutputMovie;
- DataHandler mOutputMovieDataHandler;
int mFramesPerSecond;
QSize mFrameSize;
@@ -130,17 +115,17 @@ class UBQuickTimeFile : public QThread
QString mLastErrorMessage;
- AudioStreamBasicDescription mAudioDataFormat;
-
- QPointer mWaveRecorder;
+ QString mSpatialQuality;
- CodecQ mSpatialQuality;
-
- volatile bool mSouldStopCompression;
+ volatile bool mShouldStopCompression;
volatile bool mCompressionSessionRunning;
QString mAudioRecordingDeviceName;
volatile int mPendingFrames;
+
+ AssetWriterPTR mVideoWriter;
+ AssetWriterInputPTR mVideoWriterInput;
+ AssetWriterInputAdaptorPTR mAdaptor;
};
#endif /* UBQUICKTIMEFILE_H_ */
diff --git a/src/podcast/quicktime/UBQuickTimeFile.mm b/src/podcast/quicktime/UBQuickTimeFile.mm
new file mode 100644
index 00000000..e6e18d72
--- /dev/null
+++ b/src/podcast/quicktime/UBQuickTimeFile.mm
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2013 Open Education Foundation
+ *
+ * Copyright (C) 2010-2013 Groupement d'Intérêt Public pour
+ * l'Education Numérique en Afrique (GIP ENA)
+ *
+ * This file is part of OpenBoard.
+ *
+ * OpenBoard is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License,
+ * with a specific linking exception for the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the
+ * same license as the "OpenSSL" library).
+ *
+ * OpenBoard is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with OpenBoard. If not, see .
+ */
+
+
+
+
+#include "UBQuickTimeFile.h"
+
+#include
+#import
+#import
+#import
+
+#include "UBAudioQueueRecorder.h"
+#include
+
+#include "core/memcheck.h"
+
+QQueue UBQuickTimeFile::frameQueue;
+QMutex UBQuickTimeFile::frameQueueMutex;
+QWaitCondition UBQuickTimeFile::frameBufferNotEmpty;
+
+
+UBQuickTimeFile::UBQuickTimeFile(QObject * pParent)
+ : QThread(pParent)
+ , mVideoWriter(0)
+ , mVideoWriterInput(0)
+ , mAdaptor(0)
+ , mCVPixelBufferPool(0)
+ , mFramesPerSecond(-1)
+ , mTimeScale(100)
+ , mRecordAudio(true)
+ , mShouldStopCompression(false)
+ , mCompressionSessionRunning(false)
+ , mPendingFrames(0)
+{
+ // NOOP
+}
+
+
+UBQuickTimeFile::~UBQuickTimeFile()
+{
+ // NOOP
+}
+
+bool UBQuickTimeFile::init(const QString& pVideoFileName, const QString& pProfileData, int pFramesPerSecond
+ , const QSize& pFrameSize, bool pRecordAudio, const QString& audioRecordingDevice)
+{
+ mFrameSize = pFrameSize;
+ mFramesPerSecond = pFramesPerSecond;
+ mVideoFileName = pVideoFileName;
+ mRecordAudio = pRecordAudio;
+ mSpatialQuality = pProfileData;
+
+ if (mRecordAudio)
+ mAudioRecordingDeviceName = audioRecordingDevice;
+ else
+ mAudioRecordingDeviceName = "";
+
+
+ qDebug() << "UBQuickTimeFile created; video size: " << pFrameSize.width() << " x " << pFrameSize.height();
+
+ return true;
+
+}
+
+
+void UBQuickTimeFile::run()
+{
+ mShouldStopCompression = false;
+ mPendingFrames = 0;
+
+ if (!beginSession())
+ return;
+
+ mCompressionSessionRunning = true;
+ emit compressionSessionStarted();
+
+ do {
+ frameQueueMutex.lock();
+
+ frameBufferNotEmpty.wait(&UBQuickTimeFile::frameQueueMutex);
+
+ if (!frameQueue.isEmpty()) {
+ QQueue localQueue = frameQueue;
+ frameQueue.clear();
+
+ frameQueueMutex.unlock();
+
+ while (!localQueue.isEmpty()) {
+ if ([mVideoWriterInput isReadyForMoreMediaData]) {
+ VideoFrame frame = localQueue.dequeue();
+ appendVideoFrame(frame.buffer, frame.timestamp);
+ }
+ else
+ usleep(10000);
+ }
+ }
+ else
+ frameQueueMutex.unlock();
+ } while(!mShouldStopCompression);
+
+ endSession();
+
+}
+
+/**
+ * \brief Initialize the AVAssetWriter, which handles writing the media to file
+ */
+bool UBQuickTimeFile::beginSession()
+{
+ NSError *outError;
+ NSString * outputPath = [[NSString alloc] initWithUTF8String: mVideoFileName.toUtf8().data()];
+ NSURL * outputUrl = [[NSURL alloc] initFileURLWithPath: outputPath];
+
+ if (!outputUrl) {
+ qDebug() << "Podcast video URL invalid; not recording";
+ return false;
+ }
+
+ // Create and check the assetWriter
+ mVideoWriter = [[AVAssetWriter assetWriterWithURL:outputUrl
+ fileType:AVFileTypeQuickTimeMovie
+ error:&outError] retain];
+ NSCParameterAssert(mVideoWriter);
+
+ mVideoWriter.movieTimeScale = mTimeScale;
+
+ int frameWidth = mFrameSize.width();
+ int frameHeight = mFrameSize.height();
+
+ // Create the input and check it
+ NSDictionary * videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
+ AVVideoCodecH264, AVVideoCodecKey,
+ [NSNumber numberWithInt:frameWidth], AVVideoWidthKey,
+ [NSNumber numberWithInt:frameHeight], AVVideoHeightKey,
+ nil];
+
+
+ mVideoWriterInput = [[AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
+ outputSettings:videoSettings] retain];
+ NSCParameterAssert(mVideoWriterInput);
+
+
+
+ // Pixel Buffer Adaptor. This makes it possible to pass CVPixelBuffers to the WriterInput
+ NSDictionary* pixelBufSettings = [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithInt:kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey,
+ [NSNumber numberWithInt: frameWidth], kCVPixelBufferWidthKey,
+ [NSNumber numberWithInt: frameHeight], kCVPixelBufferHeightKey,
+ nil];
+
+ mAdaptor = [[AVAssetWriterInputPixelBufferAdaptor
+ assetWriterInputPixelBufferAdaptorWithAssetWriterInput:mVideoWriterInput
+ sourcePixelBufferAttributes:pixelBufSettings] retain];
+
+
+
+ // Add the input(s) to the assetWriter
+ NSCParameterAssert([mVideoWriter canAddInput:mVideoWriterInput]);
+ [mVideoWriter addInput:mVideoWriterInput];
+
+
+ // begin the writing session
+ bool canStartWriting = [mVideoWriter startWriting];
+ [mVideoWriter startSessionAtSourceTime:CMTimeMake(0, mTimeScale)];
+
+ // return true if everything was created and started successfully
+ return (mVideoWriter != nil) && (mVideoWriterInput != nil) && canStartWriting;
+}
+
+/**
+ * \brief Close the recording sesion and finish writing the video file
+ */
+void UBQuickTimeFile::endSession()
+{
+ [mVideoWriterInput markAsFinished];
+ bool success = [mVideoWriter finishWriting];
+
+ [mAdaptor release];
+ [mVideoWriterInput release];
+ [mVideoWriter release];
+
+ mAdaptor = nil;
+ mVideoWriterInput = nil;
+ mVideoWriter = nil;
+}
+
+/**
+ * \brief Request the recording to stop
+ */
+void UBQuickTimeFile::stop()
+{
+ mShouldStopCompression = true;
+}
+
+
+/**
+ * \brief Create a CVPixelBufferRef from the input adaptor's CVPixelBufferPool
+ */
+CVPixelBufferRef UBQuickTimeFile::newPixelBuffer()
+{
+ CVPixelBufferRef pixelBuffer = 0;
+
+ if(CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, mAdaptor.pixelBufferPool, &pixelBuffer) != kCVReturnSuccess)
+ {
+ setLastErrorMessage("Could not retrieve CV buffer from pool");
+ return 0;
+ }
+
+ return pixelBuffer;
+}
+
+
+/**
+ * \brief Add a frame to the pixel buffer adaptor
+ */
+void UBQuickTimeFile::appendVideoFrame(CVPixelBufferRef pixelBuffer, long msTimeStamp)
+{
+ //qDebug() << "adding video frame at time: " << msTimeStamp;
+
+ CMTime t = CMTimeMake((msTimeStamp * mTimeScale / 1000.0), mTimeScale);
+
+ bool added = [mAdaptor appendPixelBuffer: pixelBuffer
+ withPresentationTime: t];
+
+ if (!added)
+ setLastErrorMessage(QString("Could not encode frame at time %1").arg(msTimeStamp));
+
+
+ CVPixelBufferRelease(pixelBuffer);
+}
+
+void UBQuickTimeFile::setLastErrorMessage(const QString& error)
+{
+ mLastErrorMessage = error;
+ qWarning() << "UBQuickTimeFile error" << error;
+}
+
+
+
diff --git a/src/podcast/quicktime/UBQuickTimeVideoEncoder.cpp b/src/podcast/quicktime/UBQuickTimeVideoEncoder.cpp
index ab0271be..e3497640 100644
--- a/src/podcast/quicktime/UBQuickTimeVideoEncoder.cpp
+++ b/src/podcast/quicktime/UBQuickTimeVideoEncoder.cpp
@@ -130,6 +130,14 @@ void UBQuickTimeVideoEncoder::newPixmap(const QImage& pImage, long timestamp)
}
}
+/**
+ * \brief Encode QImage into a video frame and add it to the UBQuickTimeFile's queue.
+ *
+ * This method retrieves the raw image from the supplied QImage, and uses memcpy to
+ * dump it into a CVPixelBuffer, obtained through the UBQuickTimeFile member. The
+ * pixel buffer, along with the timestamp, constitute a video frame which is added
+ * to the member UBQuickTimeFile's queue.
+ */
void UBQuickTimeVideoEncoder::encodeFrame(const QImage& pImage, long timestamp)
{
Q_ASSERT(pImage.format() == QImage::QImage::Format_RGB32); // <=> CVPixelBuffers / k32BGRAPixelFormat
@@ -157,7 +165,7 @@ void UBQuickTimeVideoEncoder::encodeFrame(const QImage& pImage, long timestamp)
const uchar* imageBuffer = pImage.bits();
- memcpy((void*) pixelBufferAddress, imageBuffer, pImage.numBytes());
+ memcpy((void*) pixelBufferAddress, imageBuffer, pImage.byteCount());
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);