blob: f70923edae4645d2677171c117899880069de2bb [file] [log] [blame]
/*
* Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
/*
* video_capture_quick_time.cc
*
*/
#include "video_capture_quick_time.h"
#include "CriticalSectionWrapper.h"
#include "event_wrapper.h"
#include "thread_wrapper.h"
#include "tick_util.h"
#include "trace.h"
#include <unistd.h>
namespace webrtc
{
VideoCaptureMacQuickTime::VideoCaptureMacQuickTime(WebRtc_Word32 iID) :
VideoCaptureImpl(iID), // super class constructor
_id(iID),
_isCapturing(false),
_captureCapability(),
_grabberCritsect(CriticalSectionWrapper::CreateCriticalSection()),
_videoMacCritsect(CriticalSectionWrapper::CreateCriticalSection()),
_terminated(true), _grabberUpdateThread(NULL),
_grabberUpdateEvent(NULL), _captureGrabber(NULL), _captureDevice(NULL),
_captureVideoType(kVideoUnknown), _captureIsInitialized(false),
_gWorld(NULL), _captureChannel(0), _captureSequence(NULL),
_sgPrepared(false), _sgStarted(false), _trueCaptureWidth(0),
_trueCaptureHeight(0), _captureDeviceList(),
_captureDeviceListTime(0), _captureCapabilityList()
{
_captureCapability.width = START_CODEC_WIDTH;
_captureCapability.height = START_CODEC_HEIGHT;
memset(_captureDeviceDisplayName, 0, sizeof(_captureDeviceDisplayName));
}
VideoCaptureMacQuickTime::~VideoCaptureMacQuickTime()
{
VideoCaptureTerminate();
if (_videoMacCritsect)
{
delete _videoMacCritsect;
}
if (_grabberCritsect)
{
delete _grabberCritsect;
}
}
WebRtc_Word32 VideoCaptureMacQuickTime::Init(
const WebRtc_Word32 id, const WebRtc_UWord8* deviceUniqueIdUTF8)
{
const WebRtc_Word32 nameLength =
(WebRtc_Word32) strlen((char*) deviceUniqueIdUTF8);
if (nameLength > kVideoCaptureUniqueNameLength)
return -1;
// Store the device name
_deviceUniqueId = new WebRtc_UWord8[nameLength + 1];
memset(_deviceUniqueId, 0, nameLength + 1);
memcpy(_deviceUniqueId, deviceUniqueIdUTF8, nameLength + 1);
// Check OSX version
OSErr err = noErr;
long version;
_videoMacCritsect->Enter();
if (!_terminated)
{
_videoMacCritsect->Leave();
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Already Initialized", __FUNCTION__, __LINE__);
return -1;
}
err = Gestalt(gestaltSystemVersion, &version);
if (err != noErr)
{
_videoMacCritsect->Leave();
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Could not retrieve OS version", __FUNCTION__,
__LINE__);
return -1;
}
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
"%s:%d OS X version: %x,", __FUNCTION__, __LINE__, version);
if (version < 0x00001040) // Older version than Mac OSX 10.4
{
_videoMacCritsect->Leave();
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d OS version not supported", __FUNCTION__, __LINE__);
return -1;
}
err = Gestalt(gestaltQuickTime, &version);
if (err != noErr)
{
_videoMacCritsect->Leave();
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Could not retrieve QuickTime version",
__FUNCTION__, __LINE__);
return -1;
}
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
"%s:%d QuickTime version: %x", __FUNCTION__, __LINE__,
version);
if (version < 0x07000000) // QT v. 7.x or newer (QT 5.0.2 0x05020000)
{
_videoMacCritsect->Leave();
return -1;
}
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture, _id,
"%s:%d EnterMovies()", __FUNCTION__, __LINE__);
EnterMovies();
if (VideoCaptureSetCaptureDevice((char*) deviceUniqueIdUTF8,
kVideoCaptureProductIdLength) == -1)
{
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d failed to set capture device: %s", __FUNCTION__,
__LINE__, deviceUniqueIdUTF8);
_videoMacCritsect->Leave();
return -1;
}
_terminated = false;
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
"%s:%d successful initialization", __FUNCTION__, __LINE__);
_videoMacCritsect->Leave();
return 0;
}
WebRtc_Word32 VideoCaptureMacQuickTime::StartCapture(
const VideoCaptureCapability& capability)
{
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id, "%s:%d "
"capability.width=%d, capability.height=%d ,capability.maxFPS=%d "
"capability.expectedCaptureDelay=%d, capability.interlaced=%d",
__FUNCTION__, __LINE__, capability.width, capability.height,
capability.maxFPS, capability.expectedCaptureDelay,
capability.interlaced);
_captureCapability.width = capability.width;
_captureCapability.height = capability.height;
if (VideoCaptureRun() == -1)
{
return -1;
}
return 0;
}
WebRtc_Word32 VideoCaptureMacQuickTime::StopCapture()
{
if (VideoCaptureStop() == -1)
{
return -1;
}
return 0;
}
bool VideoCaptureMacQuickTime::CaptureStarted()
{
return _isCapturing;
}
WebRtc_Word32 VideoCaptureMacQuickTime::CaptureSettings(
VideoCaptureCapability& settings)
{
settings.width = _captureCapability.width;
settings.height = _captureCapability.height;
settings.maxFPS = 0;
return 0;
}
int VideoCaptureMacQuickTime::VideoCaptureTerminate()
{
VideoCaptureStop();
_videoMacCritsect->Enter();
if (_terminated)
{
_videoMacCritsect->Leave();
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Already terminated", __FUNCTION__, __LINE__);
return -1;
}
_grabberCritsect->Enter();
// Stop the camera/sequence grabber
// Resets: _captureSequence, _sgStarted
StopQuickTimeCapture();
// Remove local video settings
// Resets: _gWorld, _captureCapability.width, _captureCapability.height
RemoveLocalGWorld();
DisconnectCaptureDevice();
if (_grabberUpdateThread)
_grabberUpdateThread->SetNotAlive();
_grabberCritsect->Leave();
if (_grabberUpdateEvent)
_grabberUpdateEvent->Set();
SLEEP(1);
_grabberCritsect->Enter();
if (_grabberUpdateThread)
{
_grabberUpdateThread->Stop();
delete _grabberUpdateThread;
_grabberUpdateThread = NULL;
}
if (_grabberUpdateEvent)
{
delete _grabberUpdateEvent;
_grabberUpdateEvent = NULL;
}
// Close the sequence grabber
if (_captureGrabber)
{
SGRelease(_captureGrabber);
_captureGrabber = NULL;
CloseComponent(_captureGrabber);
_captureDevice = NULL;
}
_captureVideoType = kVideoUnknown;
// Delete capture device list
ListItem* item = _captureDeviceList.First();
while (item)
{
delete static_cast<unsigned char*> (item->GetItem());
_captureDeviceList.Erase(item);
item = _captureDeviceList.First();
}
_captureDeviceListTime = 0;
_terminated = true;
_grabberCritsect->Leave();
_videoMacCritsect->Leave();
return 0;
}
int VideoCaptureMacQuickTime::UpdateCaptureSettings(int channel,
webrtc::VideoCodec& inst,
bool def)
{
if (channel < 0)
{
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Invalid channel number: %d", __FUNCTION__,
__LINE__, channel);
return -1;
}
// the size has changed, we need to change our setup
_videoMacCritsect->Enter();
// Stop capturing, if we are...
_grabberCritsect->Enter();
bool wasCapturing = false;
StopQuickTimeCapture(&wasCapturing);
// Create a new offline GWorld to receive captured frames
RemoveLocalGWorld();
if (CreateLocalGWorld(inst.width, inst.height) == -1)
{
_grabberCritsect->Leave();
_videoMacCritsect->Leave();
// Error already logged
return -1;
}
_captureCapability.width = inst.width;
_captureCapability.height = inst.height;
// Connect the capture device to our offline GWorld
// if we already have a capture device selected.
if (_captureDevice)
{
DisconnectCaptureDevice();
if (ConnectCaptureDevice() == -1)
{
// Error already logged
_grabberCritsect->Leave();
_videoMacCritsect->Leave();
return -1;
}
}
// Start capture if we did before
if (wasCapturing)
{
if (StartQuickTimeCapture() == -1)
{
_grabberCritsect->Leave();
_videoMacCritsect->Leave();
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Failed to start capturing", __FUNCTION__,
__LINE__);
return -1;
}
}
_grabberCritsect->Leave();
_videoMacCritsect->Leave();
return 0;
}
// Creates an off screen graphics world used for converting
// captured video frames if we can't get a format we want.
// Assumed protected by critsects
int VideoCaptureMacQuickTime::CreateLocalGWorld(int width, int height)
{
if (_gWorld)
{
WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideoCapture, _id,
"%s:%d GWorld already created", __FUNCTION__, __LINE__);
return -1;
}
if (width == 0 || height == 0)
{
return -1;
}
Rect captureRect;
captureRect.left = 0;
captureRect.top = 0;
captureRect.right = width;
captureRect.bottom = height;
// Create a GWorld in same size as we want to send to the codec
if (QTNewGWorld(&(_gWorld), k2vuyPixelFormat, &captureRect, 0, NULL, 0)
!= noErr)
{
return -1;
}
_captureCapability.width = width;
_captureCapability.height = height;
if (!LockPixels(GetGWorldPixMap(_gWorld)))
{
WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideoCapture, _id,
"%s:%d Could not lock pixmap. Continuing anyhow",
__FUNCTION__, __LINE__);
}
CGrafPtr theOldPort;
GDHandle theOldDevice;
GetGWorld(&theOldPort, &theOldDevice); // Gets the result from QTGetNewGWorld
SetGWorld(_gWorld, NULL); // Sets the new GWorld
BackColor( blackColor); // Changes the color on the graphic port
ForeColor( whiteColor);
EraseRect(&captureRect);
SetGWorld(theOldPort, theOldDevice);
return 0;
}
// Assumed critsect protected
int VideoCaptureMacQuickTime::RemoveLocalGWorld()
{
if (!_gWorld)
{
WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideoCapture, _id,
"%s:%d !gWorld", __FUNCTION__, __LINE__);
return -1;
}
DisposeGWorld(_gWorld);
_gWorld = NULL;
_captureCapability.width = START_CODEC_WIDTH;
_captureCapability.height = START_CODEC_HEIGHT;
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
"%s:%d GWorld has been removed", __FUNCTION__, __LINE__);
return 0;
}
// ConnectCaptureDevice
// This function prepares the capture device
// with the wanted settings, but the capture
// device isn't started.
//
// Assumed critsect protected
int VideoCaptureMacQuickTime::ConnectCaptureDevice()
{
// Prepare the capture grabber if a capture device is already set
if (!_captureGrabber)
{
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d No capture device is selected", __FUNCTION__,
__LINE__);
return -1;
}
if (_captureIsInitialized)
{
WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideoCapture, _id,
"%s:%d Capture device is already initialized",
__FUNCTION__, __LINE__);
return -1;
}
if (!_gWorld)
{
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d No GWorld is created", __FUNCTION__, __LINE__);
return -1;
}
OSErr err = noErr;
long flags = 0;
// Connect the camera to our offline GWorld
// We won't use the GWorld if we get the format we want
// from the camera.
if (SGSetGWorld(_captureGrabber, _gWorld, NULL ) != noErr)
{
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Could not connect capture device", __FUNCTION__,
__LINE__);
return -1;
}
if (SGSetDataRef(_captureGrabber, 0, 0, seqGrabDontMakeMovie) != noErr)
{
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Could not configure capture device", __FUNCTION__,
__LINE__);
return -1;
}
// Set our capture callback
if (SGSetDataProc(_captureGrabber, NewSGDataUPP(SendProcess), (long) this)
!= noErr)
{
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Could not set capture callback. Unable to receive "
"frames", __FUNCTION__, __LINE__);
return -1;
}
// Create a video channel to the sequence grabber
if (SGNewChannel(_captureGrabber, VideoMediaType, &_captureChannel)
!= noErr) // Takes time!!!
{
return -1;
}
// Get a list with all capture devices to choose the one we want.
SGDeviceList deviceList = NULL;
if (SGGetChannelDeviceList(_captureChannel, sgDeviceListIncludeInputs,
&deviceList) != noErr)
{
}
int numDevicesTypes = (*deviceList)->count;
bool captureDeviceFound = false;
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture, _id,
"%s:%d Found %d channel devices", __FUNCTION__, __LINE__,
numDevicesTypes);
// Loop through all devices to get the one we want.
for (int i = 0; i < numDevicesTypes; i++)
{
SGDeviceName deviceTypeName = (*deviceList)->entry[i];
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture, _id,
"%s:%d Inspecting device number: %d", __FUNCTION__,
__LINE__, i);
// Get the list with input devices
if (deviceTypeName.inputs)
{
SGDeviceInputList inputList = deviceTypeName.inputs;
int numInputDev = (*inputList)->count;
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture, _id,
"%s:%d Device has %d inputs", __FUNCTION__, __LINE__,
numInputDev);
for (int inputDevIndex = 0;
inputDevIndex < numInputDev;
inputDevIndex++)
{
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture,
_id, "%s:%d Inspecting input number: %d",
__FUNCTION__, __LINE__, inputDevIndex);
SGDeviceInputName deviceInputName =
(*inputList)->entry[inputDevIndex];
char devInName[64];
memset(devInName, 0, 64);
// SGDeviceInputName::name is a Str63, defined as a Pascal string.
// (Refer to MacTypes.h)
CFIndex devInNameLength =
PascalStringToCString(deviceInputName.name, devInName,
sizeof(devInName));
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture,
_id,
"%s:%d Converted pascal string with length:%d "
"to: %s", __FUNCTION__, __LINE__,
sizeof(devInName), devInName);
if (devInNameLength < 0)
{
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture,
_id,
"%s:%d Failed to convert device name from "
"pascal string to c string", __FUNCTION__,
__LINE__);
return -1;
}
if (!strcmp(devInName, _captureDeviceDisplayName))
{
WEBRTC_TRACE(webrtc::kTraceDebug,
webrtc::kTraceVideoCapture, _id,
"%s:%d We have found our device: %s",
__FUNCTION__, __LINE__,
_captureDeviceDisplayName);
if (SGSetChannelDevice(_captureChannel, deviceTypeName.name)
!= noErr)
{
WEBRTC_TRACE(webrtc::kTraceError,
webrtc::kTraceVideoCapture, _id,
"%s:%d Could not set capture device type: "
"%s",__FUNCTION__, __LINE__,
deviceTypeName.name);
return -1;
}
WEBRTC_TRACE(webrtc::kTraceInfo,
webrtc::kTraceVideoCapture, _id,
"%s:%d Capture device type is: %s",
__FUNCTION__, __LINE__, deviceTypeName.name);
if (SGSetChannelDeviceInput(_captureChannel, inputDevIndex)
!= noErr)
{
WEBRTC_TRACE(webrtc::kTraceError,
webrtc::kTraceVideoCapture, _id,
"%s:%d Could not set SG device",
__FUNCTION__, __LINE__);
return -1;
}
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture,
_id,
"%s:%d Capture device: %s has successfully "
"been set", __FUNCTION__, __LINE__,
_captureDeviceDisplayName);
captureDeviceFound = true;
break;
}
}
if (captureDeviceFound)
{
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture,
_id,
"%s:%d Capture device found, breaking from loops",
__FUNCTION__, __LINE__);
break;
}
}
}
err = SGDisposeDeviceList(_captureGrabber, deviceList);
if (!captureDeviceFound)
{
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Failed to find capture device: %s. Returning -1",
__FUNCTION__, __LINE__, _captureDeviceDisplayName);
return -1;
}
// Set the size we want from the capture device
Rect captureSize;
captureSize.left = 0;
captureSize.top = 0;
captureSize.right = _captureCapability.width;
captureSize.bottom = _captureCapability.height;
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
"%s:%d Using capture rect: l:%d t:%d r:%d b:%d", __FUNCTION__,
__LINE__, captureSize.left, captureSize.top,
captureSize.right, captureSize.bottom);
err = SGSetChannelBounds(_captureChannel, &captureSize);
if (err == noErr)
{
err = SGSetChannelUsage(_captureChannel, flags | seqGrabRecord);
}
if (err != noErr)
{
SGDisposeChannel(_captureGrabber, _captureChannel);
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Error setting SG channel to device", __FUNCTION__,
__LINE__);
return -1;
}
// Find out what video format we'll get from the capture device.
OSType compType;
err = SGGetVideoCompressorType(_captureChannel, &compType);
// Convert the Apple video format name to a VideoCapture name.
if (compType == k2vuyPixelFormat)
{
_captureVideoType = kVideoUYVY;
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
"%s:%d Device delivers UYUV formatted frames",
__FUNCTION__, __LINE__);
}
else if (compType == kYUVSPixelFormat)
{
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
"%s:%d Device delivers YUY2 formatted frames",
__FUNCTION__, __LINE__);
_captureVideoType = kVideoYUY2;
}
else
{
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture, _id,
"%s:%d Device delivers frames in an unknown format: 0x%x. "
"Consult QuickdrawTypes.h",
__FUNCTION__, __LINE__, compType);
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
"%s:%d Device delivers frames in an unknown format.",
__FUNCTION__, __LINE__);
_captureVideoType = kVideoUnknown;
}
if (SGPrepare(_captureGrabber, false, true) != noErr)
{
_grabberCritsect->Leave();
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Error starting sequence grabber", __FUNCTION__,
__LINE__);
return -1;
}
// Try to set the codec size as capture size.
err = SGSetChannelBounds(_captureChannel, &captureSize);
// Check if we really will get the size we asked for.
ImageDescriptionHandle imageDesc = (ImageDescriptionHandle) NewHandle(0);
err = SGGetChannelSampleDescription(_captureChannel, (Handle) imageDesc);
_trueCaptureWidth = (**imageDesc).width;
_trueCaptureHeight = (**imageDesc).height;
DisposeHandle((Handle) imageDesc);
_captureIsInitialized = true;
_sgPrepared = true;
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
"%s:%d Success starting sequence grabber", __FUNCTION__,
__LINE__);
return 0;
}
// Assumed critsect protected
int VideoCaptureMacQuickTime::DisconnectCaptureDevice()
{
if (_sgStarted)
{
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Capture device is still running. Returning -1",
__FUNCTION__, __LINE__);
return -1;
}
if (!_sgPrepared)
{
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d No capture device connected", __FUNCTION__,
__LINE__);
return -1;
}
// Close the capture channel
SGStop(_captureGrabber);
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture, _id,
"%s:%d !!!! releasing sg stuff", __FUNCTION__, __LINE__);
SGDisposeChannel(_captureGrabber, _captureChannel);
SGRelease(_captureGrabber);
CloseComponent(_captureGrabber);
// Reset all values
_captureChannel = NULL;
_captureVideoType = kVideoUnknown;
_trueCaptureWidth = 0;
_trueCaptureHeight = 0;
_captureIsInitialized = false;
_sgPrepared = false;
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
"%s:%d Sequence grabber removed", __FUNCTION__, __LINE__);
return 0;
}
// StartQuickTimeCapture
//
// Actually starts the camera
//
int VideoCaptureMacQuickTime::StartQuickTimeCapture()
{
_grabberCritsect->Enter();
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
"%s:%d Attempting to start sequence grabber", __FUNCTION__,
__LINE__);
if (_sgStarted)
{
_grabberCritsect->Leave();
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Sequence grabber already started", __FUNCTION__,
__LINE__);
return 0;
}
if (!_sgPrepared)
{
_grabberCritsect->Leave();
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Sequence grabber not prepared properly",
__FUNCTION__, __LINE__);
return 0;
}
if (SGStartRecord(_captureGrabber) != noErr)
{
_grabberCritsect->Leave();
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Error starting sequence grabber", __FUNCTION__,
__LINE__);
return -1;
}
Rect captureRect = { 0, 0, 0, 0 };
MatrixRecord scaleMatrix;
ImageDescriptionHandle imageDesc = (ImageDescriptionHandle) NewHandle(0);
// Get the sample description for the channel, which is the same as for the
// capture device
if (SGGetChannelSampleDescription(_captureChannel, (Handle) imageDesc)
!= noErr)
{
_grabberCritsect->Leave();
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Error accessing device properties", __FUNCTION__,
__LINE__);
return -1;
}
// Create a scale matrix to scale the captured image
// Needed if we don't get the size wanted from the camera
captureRect.right = (**imageDesc).width;
captureRect.bottom = (**imageDesc).height;
Rect codecRect;
codecRect.left = 0;
codecRect.top = 0;
codecRect.right = _captureCapability.width;
codecRect.bottom = _captureCapability.height;
RectMatrix(&scaleMatrix, &captureRect, &codecRect);
// Start grabbing images from the capture device to _gWorld
if (DecompressSequenceBegin(&_captureSequence, imageDesc, _gWorld, NULL,
NULL, &scaleMatrix, srcCopy, (RgnHandle) NULL,
NULL, codecNormalQuality, bestSpeedCodec)
!= noErr)
{
_grabberCritsect->Leave();
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
"%s:%d Error starting decompress sequence", __FUNCTION__,
__LINE__);
return -1;
}
DisposeHandle((Handle) imageDesc);
_sgStarted = true;
_grabberCritsect->Leave();
return 0;
}
int VideoCaptureMacQuickTime::StopQuickTimeCapture(bool* wasCapturing)
{
_grabberCritsect->Enter();
WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCapture, _id,
"%s:%d wasCapturing=%d", __FUNCTION__, __LINE__, wasCapturing);
if (!_sgStarted)
{
if (wasCapturing)
*wasCapturing = false;
_grabberCritsect->Leave();
WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideoCapture, _id,
"%s:%d Sequence grabber was never started", __FUNCTION__,
__LINE__);
return 0;
}
if (wasCapturing)
*wasCapturing = true;
OSErr error = noErr;
error = SGStop(_captureGrabber);
CDSequenceEnd(_captureSequence);
_captureSequence = NULL;
_sgStarted = false;
_grabberCritsect->Leave();
if (error != noErr)
{
WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideoCapture, _id,
"%s:%d Could not stop sequence grabber", __FUNCTION__,
__LINE__);
return -1;
}
return 0;
}
//-------------------------------------------------
//
// Thread/function to keep capture device working
//
//-------------------------------------------------
//
// GrabberUpdateThread / GrabberUpdateProcess
//
// Called at a certain time interval to tell
// the capture device / SequenceGrabber to
// actually work.
bool VideoCaptureMacQuickTime::GrabberUpdateThread(void* obj)
{
return static_cast<VideoCaptureMacQuickTime*> (obj)->GrabberUpdateProcess();
}
bool VideoCaptureMacQuickTime::GrabberUpdateProcess()
{
_grabberUpdateEvent->Wait(30);
if (_isCapturing == false)
return false;
_grabberCritsect->Enter();
if (_captureGrabber)
{
if (SGIdle(_captureGrabber) != noErr)
{
}
}
_grabberCritsect->Leave();
return true;
}
//
// VideoCaptureStop
//
// Stops the capture device
//
int VideoCaptureMacQuickTime::VideoCaptureStop()
{
if (_grabberUpdateThread)
{
_grabberUpdateThread->Stop();
}
_videoMacCritsect->Enter();
_grabberCritsect->Enter();
int retVal = StopQuickTimeCapture();
_grabberCritsect->Leave();
_videoMacCritsect->Leave();
if (retVal == -1)
{
return -1;
}
_isCapturing = false;
return 0;
}
//
// VideoCaptureRun
//
// Starts the capture device and creates
// the update thread.
//
int VideoCaptureMacQuickTime::VideoCaptureRun()
{
_videoMacCritsect->Enter();
_grabberCritsect->Enter();
int res = StartQuickTimeCapture();
// Create the thread for updating sequence grabber if not created earlier
if (!_grabberUpdateThread)
{
_grabberUpdateEvent = EventWrapper::Create();
_grabberUpdateThread = ThreadWrapper::CreateThread(
VideoCaptureMacQuickTime::GrabberUpdateThread, this, kHighPriority);
unsigned int id;
_grabberUpdateThread->Start(id);
}
else
{
unsigned int id;
_grabberUpdateThread->Start(id);
}
_grabberCritsect->Leave();
_videoMacCritsect->Leave();
_isCapturing = true;
return res;
}
// ----------------------------------------------------------------------
//
// SendProcess
// sequence grabber data procedure
//
// This function is called by the capture device as soon as a new
// frame is available.
//
//
// SendFrame
//
// The non-static function used by the capture device callback
//
// Input:
// sgChannel: the capture device channel generating the callback
// data: the video frame
// length: the data length in bytes
// grabTime: time stamp generated by the capture device / sequece grabber
//
// ----------------------------------------------------------------------
OSErr VideoCaptureMacQuickTime::SendProcess(SGChannel sgChannel, Ptr p,
long len, long* /*offset*/,
long /*chRefCon*/, TimeValue time,
short /*writeType*/, long refCon)
{
VideoCaptureMacQuickTime* videoEngine =
reinterpret_cast<VideoCaptureMacQuickTime*> (refCon);
return videoEngine->SendFrame(sgChannel, (char*) p, len, time);
}
int VideoCaptureMacQuickTime::SendFrame(SGChannel /*sgChannel*/, char* data,
long length, TimeValue /*grabTime*/)
{
if (!_sgPrepared)
{
WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideoCapture, _id,
"%s:%d Sequence Grabber is not initialized", __FUNCTION__,
__LINE__);
return 0;
}
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture, _id,
"%s:%d Frame has been delivered\n", __FUNCTION__, __LINE__);
CodecFlags ignore;
_grabberCritsect->Enter();
if (_gWorld)
{
// Will be set to true if we don't recognize the size and/or video
// format.
bool convertFrame = false;
WebRtc_Word32 width = 352;
WebRtc_Word32 height = 288;
WebRtc_Word32 frameSize = 0;
VideoCaptureCapability captureCapability;
captureCapability.width = width;
captureCapability.height = height;
captureCapability.maxFPS = 30;
switch (_captureVideoType)
{
case kVideoUYVY:
captureCapability.rawType = kVideoUYVY;
break;
case kVideoYUY2:
captureCapability.rawType = kVideoYUY2;
break;
case kVideoI420:
captureCapability.rawType = kVideoI420;
break;
default:
captureCapability.rawType = kVideoI420;
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture,
_id, "%s:%d raw = I420 by default\n",
__FUNCTION__, __LINE__);
break;
}
// Convert the camera video type to something VideoEngine can work with
// Check if we need to downsample the incomming frame.
switch (_captureVideoType)
{
case kVideoUYVY:
case kVideoYUY2:
frameSize = (width * height * 16) >> 3; // 16 is for YUY2 format
if (width == _captureCapability.width || height
== _captureCapability.height)
{
// Ok format and size, send the frame to super class
IncomingFrame((WebRtc_UWord8*) data,
(WebRtc_Word32) frameSize, captureCapability,
TickTime::MillisecondTimestamp());
}
else if (width == _trueCaptureWidth && height
== _trueCaptureHeight)
{
// We need to scale the picture to correct size...
// This happens for cameras not supporting all sizes.
// E.g. older built-in iSight doesn't support QCIF.
// Convert the incoming frame into our GWorld.
int res =
DecompressSequenceFrameS(_captureSequence, data,
length, 0, &ignore, NULL);
if (res != noErr && res != -8976) // 8796 == black frame
{
WEBRTC_TRACE(webrtc::kTraceWarning,
webrtc::kTraceVideoCapture, _id,
"%s:%d Captured black frame. Not "
"processing it", __FUNCTION__, __LINE__);
_grabberCritsect->Leave();
return 0;
}
// Copy the frame from the PixMap to our video buffer
PixMapHandle pixMap = GetGWorldPixMap(_gWorld);
// Lock the image data in the GWorld.
LockPixels(pixMap);
// Get a pointer to the pixel data.
Ptr capturedFrame = GetPixBaseAddr(pixMap);
// Send the converted frame out to super class
IncomingFrame((WebRtc_UWord8*) data,
(WebRtc_Word32) frameSize, captureCapability,
TickTime::MillisecondTimestamp());
// Unlock the image data to get ready for the next frame.
UnlockPixels(pixMap);
}
else
{
// Not a size we recognize, use the Mac internal scaling...
convertFrame = true;
WEBRTC_TRACE(webrtc::kTraceDebug,
webrtc::kTraceVideoCapture, _id,
"%s:%d Not correct incoming stream size for "
"the format and configured size",
__FUNCTION__, __LINE__);
}
break;
default:
// Not a video format we recognize, use the Mac internal scaling
WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideoCapture,
_id, "%s:%d Unknown video frame format (default)",
__FUNCTION__, __LINE__);
convertFrame = true;
break;
}
if (convertFrame)
{
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture, _id,
"%s:%d Unrecognized frame format. Converting frame",
__FUNCTION__, __LINE__);
// We don't recognise the input format. Convert to UYVY, I420 is not
// supported on osx. Decompress the grabbed frame into the GWorld,
// i.e from webcam format to ARGB (RGB24), and extract the frame.
int res = DecompressSequenceFrameS(_captureSequence, data, length,
0, &ignore, NULL);
if (res != noErr && res != -8976) // 8796 means a black frame
{
_grabberCritsect->Leave();
return 0;
}
// Copy the frame from the PixMap to our video buffer
PixMapHandle rgbPixMap = GetGWorldPixMap(_gWorld);
LockPixels(rgbPixMap);
Ptr capturedFrame = GetPixBaseAddr(rgbPixMap);
// Get the picture size
int width = (*rgbPixMap)->bounds.right;
int height = (*rgbPixMap)->bounds.bottom;
// 16 is for YUY2 format.
WebRtc_Word32 frameSize = (width * height * 16) >> 3;
// Ok format and size, send the frame to super class
IncomingFrame((WebRtc_UWord8*) data, (WebRtc_Word32) frameSize,
captureCapability, TickTime::MillisecondTimestamp());
UnlockPixels(rgbPixMap);
}
// Tell the capture device it's ok to update.
SGUpdate(_captureGrabber, NULL);
}
else
{
WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceVideoCapture, _id,
"%s:%d No GWorld created, but frames are being delivered",
__FUNCTION__, __LINE__);
}
_grabberCritsect->Leave();
return 0;
}
int VideoCaptureMacQuickTime::VideoCaptureInitThreadContext()
{
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture, _id,
"%s:%d ", __FUNCTION__, __LINE__);
_videoMacCritsect->Enter();
EnterMoviesOnThread( kQTEnterMoviesFlagDontSetComponentsThreadMode);
_videoMacCritsect->Leave();
return 0;
}
//
//
// Functions for handling capture devices
//
//
VideoCaptureMacQuickTime::VideoCaptureMacName::VideoCaptureMacName() :
_size(0)
{
memset(_name, 0, kVideoCaptureMacNameMaxSize);
}
int VideoCaptureMacQuickTime::VideoCaptureSetCaptureDevice(
const char* deviceName, int size)
{
_videoMacCritsect->Enter();
bool wasCapturing = false;
_grabberCritsect->Enter();
if (_captureGrabber)
{
// Stop grabbing, disconnect and close the old capture device
StopQuickTimeCapture(&wasCapturing);
DisconnectCaptureDevice();
CloseComponent(_captureGrabber);
_captureDevice = NULL;
_captureGrabber = NULL;
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Old capture device removed", __FUNCTION__,
__LINE__);
}
if (deviceName == NULL || size == 0)
{
_grabberCritsect->Leave();
_videoMacCritsect->Leave();
return 0;
}
if (size < 0)
{
_grabberCritsect->Leave();
_videoMacCritsect->Leave();
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d 'size' is not valid", __FUNCTION__, __LINE__);
return 0;
}
ComponentDescription compCaptureType;
// Define the component we want to open
compCaptureType.componentType = SeqGrabComponentType;
compCaptureType.componentSubType = 0;
compCaptureType.componentManufacturer = 0;
compCaptureType.componentFlags = 0;
compCaptureType.componentFlagsMask = 0;
long numSequenceGrabbers = CountComponents(&compCaptureType);
// loop through the available grabbers and open the first possible
for (int i = 0; i < numSequenceGrabbers; i++)
{
_captureDevice = FindNextComponent(0, &compCaptureType);
_captureGrabber = OpenComponent(_captureDevice);
if (_captureGrabber != NULL)
{
// We've found a sequencegrabber that we could open
if (SGInitialize(_captureGrabber) != noErr)
{
_grabberCritsect->Leave();
_videoMacCritsect->Leave();
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture,
_id,
"%s:%d Could not initialize sequence grabber",
__FUNCTION__, __LINE__);
return -1;
}
break;
}
if (i == numSequenceGrabbers - 1)
{
// Couldn't open a sequence grabber
_grabberCritsect->Leave();
_videoMacCritsect->Leave();
WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCapture, _id,
"%s:%d Could not open a sequence grabber",
__FUNCTION__, __LINE__);
return -1;
}
}
if (!_gWorld)
{
// We don't have a GWorld. Create one to enable early preview
// without calling SetSendCodec
if (CreateLocalGWorld(_captureCapability.width,
_captureCapability.height) == -1)
{
// Error already logged
_grabberCritsect->Leave();
_videoMacCritsect->Leave();
return -1;
}
}
// Connect the camera with our GWorld
int cpySize = size;
if ((unsigned int) size > sizeof(_captureDeviceDisplayName))
{
cpySize = sizeof(_captureDeviceDisplayName);
}
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture, _id,
"%s:%d Copying %d chars from deviceName to "
"_captureDeviceDisplayName (size=%d)\n",
__FUNCTION__, __LINE__, cpySize, size);
memcpy(_captureDeviceDisplayName, deviceName, cpySize);
if (ConnectCaptureDevice() == -1)
{
// Error already logged
_grabberCritsect->Leave();
_videoMacCritsect->Leave();
return -1;
}
if (StartQuickTimeCapture() == -1)
{
// Error already logged
_grabberCritsect->Leave();
_videoMacCritsect->Leave();
return -1;
}
_grabberCritsect->Leave();
_videoMacCritsect->Leave();
return 0;
}
bool VideoCaptureMacQuickTime::IsCaptureDeviceSelected()
{
_grabberCritsect->Leave();
return (_captureIsInitialized) ? true : false;
_grabberCritsect->Leave();
}
/**
Convert a Pascal string to a C string.
\param[in] pascalString
Pascal string to convert. Pascal strings contain the number of
characters in the first byte and are not null-terminated.
\param[out] cString
The C string buffer into which to copy the converted string.
\param[in] bufferSize
The size of the C string buffer in bytes.
\return The number of characters in the string on success and -1 on failure.
*/
CFIndex VideoCaptureMacQuickTime::PascalStringToCString(
const unsigned char* pascalString, char* cString, CFIndex bufferSize)
{
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture, 0,
"%s:%d Converting pascal string to c string", __FUNCTION__,
__LINE__);
if (pascalString == NULL)
{
return -1;
}
if (cString == NULL)
{
return -1;
}
if (bufferSize == 0)
{
return -1;
}
CFIndex cStringLength = 0;
CFIndex maxStringLength = bufferSize - 1;
CFStringRef cfString = CFStringCreateWithPascalString(
NULL, pascalString, kCFStringEncodingMacRoman);
if (cfString == NULL)
{
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture, 0,
"%s:%d Error in CFStringCreateWithPascalString()",
__FUNCTION__, __LINE__);
CFRelease(cfString);
return -1;
}
CFIndex cfLength = CFStringGetLength(cfString);
cStringLength = cfLength;
if (cfLength > maxStringLength)
{
cStringLength = maxStringLength;
}
Boolean success = CFStringGetCString(cfString, cString, bufferSize,
kCFStringEncodingMacRoman);
// Ensure the problem isn't insufficient buffer length.
// This is fine; we will return a partial string.
if (success == false && cfLength <= maxStringLength)
{
WEBRTC_TRACE(webrtc::kTraceDebug, webrtc::kTraceVideoCapture, 0,
"%s:%d Error in CFStringGetCString()", __FUNCTION__,
__LINE__);
CFRelease(cfString);
return -1;
}
CFRelease(cfString);
return cStringLength;
}
} // namespace webrtc