mirror of
https://github.com/pion/mediadevices.git
synced 2025-09-26 20:41:46 +08:00
Add darwin camera support
* Add avfoundation Go and C bindings * Add darwin camera adapter * Add darwin camera support to README
This commit is contained in:
@@ -8,7 +8,7 @@ Go implementation of the [MediaDevices](https://developer.mozilla.org/en-US/docs
|
||||
|
||||
| Interface | Linux | Mac | Windows |
|
||||
| :--------: | :---: | :-: | :-----: |
|
||||
| Camera | ✔️ | ✖️ | ✔️ |
|
||||
| Camera | ✔️ | ✔️ | ✔️ |
|
||||
| Microphone | ✔️ | ✖️ | ✔️ |
|
||||
| Screen | ✔️ | ✖️ | ✖️ |
|
||||
|
||||
@@ -17,15 +17,15 @@ Go implementation of the [MediaDevices](https://developer.mozilla.org/en-US/docs
|
||||
| OS | Library/Interface |
|
||||
| :-----: | :---------------------------------------------------------------------: |
|
||||
| Linux | [Video4Linux](https://en.wikipedia.org/wiki/Video4Linux) |
|
||||
| Mac | N/A |
|
||||
| Mac | [AVFoundation](https://developer.apple.com/av-foundation/) |
|
||||
| Windows | [DirectShow](https://docs.microsoft.com/en-us/windows/win32/directshow) |
|
||||
|
||||
| Pixel Format | Linux | Mac | Windows |
|
||||
| :---------------------------------------------------: | :---: | :-: | :-----: |
|
||||
| [YUY2](https://www.fourcc.org/pixel-format/yuv-yuy2/) | ✔️ | ✖️ | ✔️ |
|
||||
| [UYVY](https://www.fourcc.org/pixel-format/yuv-uyvy/) | ✔️ | ✖️ | ✖️ |
|
||||
| [UYVY](https://www.fourcc.org/pixel-format/yuv-uyvy/) | ✔️ | ✔️ | ✖️ |
|
||||
| [I420](https://www.fourcc.org/pixel-format/yuv-i420/) | ✔️ | ✖️ | ✖️ |
|
||||
| [NV21](https://www.fourcc.org/pixel-format/yuv-nv21/) | ✔️ | ✖️ | ✖️ |
|
||||
| [NV21](https://www.fourcc.org/pixel-format/yuv-nv21/) | ✔️ | ✔️ | ✖️ |
|
||||
| [MJPEG](https://www.fourcc.org/mjpg/) | ✔️ | ✖️ | ✖️ |
|
||||
|
||||
### Microphone
|
||||
|
25
pkg/avfoundation/.gitignore
vendored
Normal file
25
pkg/avfoundation/.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
#User settings
|
||||
xcuserdata/
|
||||
|
||||
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
|
||||
*.xcscmblueprint
|
||||
*.xccheckout
|
||||
|
||||
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
|
||||
build/
|
||||
DerivedData/
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
|
||||
## Gcc Patch
|
||||
/*.gcno
|
||||
.DS_STORE
|
||||
|
||||
Build/
|
294
pkg/avfoundation/AVFoundationBind.xcodeproj/project.pbxproj
Normal file
294
pkg/avfoundation/AVFoundationBind.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,294 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
F0143CC12479F78E00EC29C9 /* AVFoundationBind.h in Headers */ = {isa = PBXBuildFile; fileRef = F0143CC02479F78E00EC29C9 /* AVFoundationBind.h */; };
|
||||
F0143CC32479F78E00EC29C9 /* AVFoundationBind.m in Sources */ = {isa = PBXBuildFile; fileRef = F0143CC22479F78E00EC29C9 /* AVFoundationBind.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
F0143CBD2479F78E00EC29C9 /* libAVFoundationBind.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAVFoundationBind.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F0143CC02479F78E00EC29C9 /* AVFoundationBind.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AVFoundationBind.h; sourceTree = "<group>"; };
|
||||
F0143CC22479F78E00EC29C9 /* AVFoundationBind.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AVFoundationBind.m; sourceTree = "<group>"; };
|
||||
F0FDDA0B247E15D900A3429D /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
F0143CBB2479F78E00EC29C9 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
F0143CB42479F78E00EC29C9 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F0143CBF2479F78E00EC29C9 /* AVFoundationBind */,
|
||||
F0143CBE2479F78E00EC29C9 /* Products */,
|
||||
F0FDDA0A247E15D900A3429D /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F0143CBE2479F78E00EC29C9 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F0143CBD2479F78E00EC29C9 /* libAVFoundationBind.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F0143CBF2479F78E00EC29C9 /* AVFoundationBind */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F0143CC02479F78E00EC29C9 /* AVFoundationBind.h */,
|
||||
F0143CC22479F78E00EC29C9 /* AVFoundationBind.m */,
|
||||
);
|
||||
path = AVFoundationBind;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F0FDDA0A247E15D900A3429D /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F0FDDA0B247E15D900A3429D /* AVFoundation.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
F0143CB92479F78E00EC29C9 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F0143CC12479F78E00EC29C9 /* AVFoundationBind.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
F0143CBC2479F78E00EC29C9 /* AVFoundationBind */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = F0143CC62479F78E00EC29C9 /* Build configuration list for PBXNativeTarget "AVFoundationBind" */;
|
||||
buildPhases = (
|
||||
F0143CB92479F78E00EC29C9 /* Headers */,
|
||||
F0143CBA2479F78E00EC29C9 /* Sources */,
|
||||
F0143CBB2479F78E00EC29C9 /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = AVFoundationBind;
|
||||
productName = AVFoundationBind;
|
||||
productReference = F0143CBD2479F78E00EC29C9 /* libAVFoundationBind.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
F0143CB52479F78E00EC29C9 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1150;
|
||||
ORGANIZATIONNAME = "Herman, Lukas";
|
||||
TargetAttributes = {
|
||||
F0143CBC2479F78E00EC29C9 = {
|
||||
CreatedOnToolsVersion = 11.5;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = F0143CB82479F78E00EC29C9 /* Build configuration list for PBXProject "AVFoundationBind" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = F0143CB42479F78E00EC29C9;
|
||||
productRefGroup = F0143CBE2479F78E00EC29C9 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
F0143CBC2479F78E00EC29C9 /* AVFoundationBind */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
F0143CBA2479F78E00EC29C9 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F0143CC32479F78E00EC29C9 /* AVFoundationBind.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
F0143CC42479F78E00EC29C9 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
F0143CC52479F78E00EC29C9 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = macosx;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
F0143CC72479F78E00EC29C9 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
EXECUTABLE_PREFIX = lib;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
F0143CC82479F78E00EC29C9 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
EXECUTABLE_PREFIX = lib;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
F0143CB82479F78E00EC29C9 /* Build configuration list for PBXProject "AVFoundationBind" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
F0143CC42479F78E00EC29C9 /* Debug */,
|
||||
F0143CC52479F78E00EC29C9 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
F0143CC62479F78E00EC29C9 /* Build configuration list for PBXNativeTarget "AVFoundationBind" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
F0143CC72479F78E00EC29C9 /* Debug */,
|
||||
F0143CC82479F78E00EC29C9 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = F0143CB52479F78E00EC29C9 /* Project object */;
|
||||
}
|
7
pkg/avfoundation/AVFoundationBind.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
pkg/avfoundation/AVFoundationBind.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:AVFoundationBind.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1150"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F0143CBC2479F78E00EC29C9"
|
||||
BuildableName = "libAVFoundationBind.a"
|
||||
BlueprintName = "AVFoundationBind"
|
||||
ReferencedContainer = "container:AVFoundationBind.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F0143CBC2479F78E00EC29C9"
|
||||
BuildableName = "libAVFoundationBind.a"
|
||||
BlueprintName = "AVFoundationBind"
|
||||
ReferencedContainer = "container:AVFoundationBind.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
78
pkg/avfoundation/AVFoundationBind/AVFoundationBind.h
Normal file
78
pkg/avfoundation/AVFoundationBind/AVFoundationBind.h
Normal file
@@ -0,0 +1,78 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2019-2020 Pion
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#define MAX_DEVICES 8
|
||||
#define MAX_PROPERTIES 64
|
||||
#define MAX_DEVICE_UID_CHARS 64
|
||||
|
||||
typedef const char* STATUS;
|
||||
static STATUS STATUS_OK = (STATUS) NULL;
|
||||
static STATUS STATUS_NULL_ARG = (STATUS) "One of the arguments was null";
|
||||
static STATUS STATUS_DEVICE_INIT_FAILED = (STATUS) "Failed to init device";
|
||||
static STATUS STATUS_UNSUPPORTED_FRAME_FORMAT = (STATUS) "Unsupported frame format";
|
||||
static STATUS STATUS_UNSUPPORTED_MEDIA_TYPE = (STATUS) "Unsupported media type";
|
||||
static STATUS STATUS_FAILED_TO_ACQUIRE_LOCK = (STATUS) "Failed to acquire a lock";
|
||||
static STATUS STATUS_UNSUPPORTED_FORMAT = (STATUS) "Unsupported device format";
|
||||
|
||||
typedef enum AVBindMediaType {
|
||||
AVBindMediaTypeVideo,
|
||||
AVBindMediaTypeAudio,
|
||||
} AVBindMediaType;
|
||||
|
||||
typedef enum AVBindFrameFormat {
|
||||
AVBindFrameFormatI420,
|
||||
AVBindFrameFormatNV21,
|
||||
AVBindFrameFormatYUY2,
|
||||
AVBindFrameFormatUYVY,
|
||||
} AVBindFrameFormat;
|
||||
|
||||
typedef void (*AVBindDataCallback)(void *userData, void *buf, int len);
|
||||
|
||||
typedef struct AVBindMediaProperty {
|
||||
// video property
|
||||
int width, height;
|
||||
AVBindFrameFormat frameFormat;
|
||||
|
||||
// audio property
|
||||
|
||||
} AVBindMediaProperty, *PAVBindMediaProperty;
|
||||
|
||||
typedef struct AVBindSession AVBindSession, *PAVBindSession;
|
||||
|
||||
typedef struct {
|
||||
char uid[MAX_DEVICE_UID_CHARS + 1];
|
||||
} AVBindDevice, *PAVBindDevice;
|
||||
|
||||
// AVBindDevices returns a list of AVBindDevices. The result array is pointing to a static
|
||||
// memory. The caller is expected to not hold on to the address for a long time and make a copy.
|
||||
// Everytime this function gets called, the array will be overwritten and the memory will be reused.
|
||||
STATUS AVBindDevices(AVBindMediaType, PAVBindDevice*, int*);
|
||||
|
||||
STATUS AVBindSessionInit(AVBindDevice, PAVBindSession*);
|
||||
STATUS AVBindSessionFree(PAVBindSession*);
|
||||
STATUS AVBindSessionOpen(PAVBindSession, AVBindMediaProperty, AVBindDataCallback, void*);
|
||||
STATUS AVBindSessionClose(PAVBindSession);
|
||||
STATUS AVBindSessionProperties(PAVBindSession, PAVBindMediaProperty*, int*);
|
350
pkg/avfoundation/AVFoundationBind/AVFoundationBind.m
Normal file
350
pkg/avfoundation/AVFoundationBind/AVFoundationBind.m
Normal file
@@ -0,0 +1,350 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2019-2020 Pion
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
// Naming Convention (let "name" as an actual variable name):
|
||||
// - mName: "name" is a member of an Objective C object
|
||||
// - pName: "name" is a C pointer
|
||||
// - refName: "name" is an Objective C object reference
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import "AVFoundationBind.h"
|
||||
#include <string.h>
|
||||
|
||||
#define CHK(condition, status) \
|
||||
do { \
|
||||
if(!(condition)) { \
|
||||
retStatus = status; \
|
||||
goto cleanup; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define CHK_STATUS(status) \
|
||||
do { \
|
||||
if(status != STATUS_OK) { \
|
||||
retStatus = status; \
|
||||
goto cleanup; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
@interface VideoDataDelegate : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate>
|
||||
|
||||
@property (readonly) AVBindDataCallback mCallback;
|
||||
@property (readonly) void *mPUserData;
|
||||
|
||||
- (void)captureOutput:(AVCaptureOutput *)captureOutput
|
||||
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
|
||||
fromConnection:(AVCaptureConnection *)connection;
|
||||
|
||||
@end
|
||||
|
||||
@implementation VideoDataDelegate
|
||||
|
||||
- (id) init: (AVBindDataCallback) callback
|
||||
withUserData: (void*) pUserData {
|
||||
self = [super init];
|
||||
_mCallback = callback;
|
||||
_mPUserData = pUserData;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)captureOutput:(AVCaptureOutput *)captureOutput
|
||||
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
|
||||
fromConnection:(AVCaptureConnection *)connection {
|
||||
if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 ||
|
||||
!CMSampleBufferIsValid(sampleBuffer) ||
|
||||
!CMSampleBufferDataIsReady(sampleBuffer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
|
||||
if (imageBuffer == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
imageBuffer = CVBufferRetain(imageBuffer);
|
||||
CVReturn ret =
|
||||
CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
if (ret != kCVReturnSuccess) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t heightY = CVPixelBufferGetHeightOfPlane(imageBuffer, 0);
|
||||
size_t bytesPerRowY = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
|
||||
|
||||
size_t heightUV = CVPixelBufferGetHeightOfPlane(imageBuffer, 1);
|
||||
size_t bytesPerRowUV = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1);
|
||||
|
||||
int len = (int)((heightY * bytesPerRowY) + (2 * heightUV * bytesPerRowUV));
|
||||
void *buf = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
|
||||
_mCallback(_mPUserData, buf, len);
|
||||
|
||||
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
|
||||
CVBufferRelease(imageBuffer);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface AudioDataDelegate : NSObject<AVCaptureAudioDataOutputSampleBufferDelegate>
|
||||
|
||||
@property (readonly) AVBindDataCallback mCallback;
|
||||
|
||||
- (void)captureOutput:(AVCaptureOutput *)captureOutput
|
||||
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
|
||||
fromConnection:(AVCaptureConnection *)connection;
|
||||
|
||||
@end
|
||||
|
||||
@implementation AudioDataDelegate
|
||||
|
||||
- (id) init: (AVBindDataCallback) callback {
|
||||
self = [super init];
|
||||
_mCallback = callback;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)captureOutput:(AVCaptureOutput *)captureOutput
|
||||
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
|
||||
fromConnection:(AVCaptureConnection *)connection {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
STATUS frameFormatToFourCC(AVBindFrameFormat format, FourCharCode *pFourCC) {
|
||||
STATUS retStatus = STATUS_OK;
|
||||
switch (format) {
|
||||
case AVBindFrameFormatNV21:
|
||||
*pFourCC = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
|
||||
break;
|
||||
case AVBindFrameFormatUYVY:
|
||||
*pFourCC = kCVPixelFormatType_422YpCbCr8;
|
||||
break;
|
||||
// TODO: Add the rest of frame formats
|
||||
default:
|
||||
retStatus = STATUS_UNSUPPORTED_FRAME_FORMAT;
|
||||
}
|
||||
return retStatus;
|
||||
}
|
||||
|
||||
STATUS frameFormatFromFourCC(FourCharCode fourCC, AVBindFrameFormat *pFormat) {
|
||||
STATUS retStatus = STATUS_OK;
|
||||
switch (fourCC) {
|
||||
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
|
||||
*pFormat = AVBindFrameFormatNV21;
|
||||
break;
|
||||
case kCVPixelFormatType_422YpCbCr8:
|
||||
*pFormat = AVBindFrameFormatUYVY;
|
||||
break;
|
||||
// TODO: Add the rest of frame formats
|
||||
default:
|
||||
retStatus = STATUS_UNSUPPORTED_FRAME_FORMAT;
|
||||
}
|
||||
return retStatus;
|
||||
}
|
||||
|
||||
|
||||
STATUS AVBindDevices(AVBindMediaType mediaType, PAVBindDevice *ppDevices, int *pLen) {
|
||||
static AVBindDevice devices[MAX_DEVICES];
|
||||
STATUS retStatus = STATUS_OK;
|
||||
NSAutoreleasePool *refPool = [[NSAutoreleasePool alloc] init];
|
||||
CHK(mediaType == AVBindMediaTypeVideo || mediaType == AVBindMediaTypeAudio, STATUS_UNSUPPORTED_MEDIA_TYPE);
|
||||
CHK(ppDevices != NULL && pLen != NULL, STATUS_NULL_ARG);
|
||||
|
||||
PAVBindDevice pDevice;
|
||||
AVMediaType _mediaType = mediaType == AVBindMediaTypeVideo ? AVMediaTypeVideo : AVMediaTypeAudio;
|
||||
NSArray *refAllTypes = @[
|
||||
AVCaptureDeviceTypeBuiltInWideAngleCamera,
|
||||
AVCaptureDeviceTypeBuiltInMicrophone,
|
||||
AVCaptureDeviceTypeExternalUnknown
|
||||
];
|
||||
AVCaptureDeviceDiscoverySession *refSession = [AVCaptureDeviceDiscoverySession
|
||||
discoverySessionWithDeviceTypes: refAllTypes
|
||||
mediaType: _mediaType
|
||||
position: AVCaptureDevicePositionUnspecified];
|
||||
|
||||
int i = 0;
|
||||
for (AVCaptureDevice *refDevice in refSession.devices) {
|
||||
if (i >= MAX_DEVICES) {
|
||||
break;
|
||||
}
|
||||
|
||||
pDevice = devices + i;
|
||||
strncpy(pDevice->uid, refDevice.uniqueID.UTF8String, MAX_DEVICE_UID_CHARS);
|
||||
pDevice->uid[MAX_DEVICE_UID_CHARS] = '\0';
|
||||
i++;
|
||||
}
|
||||
|
||||
*ppDevices = devices;
|
||||
*pLen = i;
|
||||
|
||||
cleanup:
|
||||
[refPool drain];
|
||||
return retStatus;
|
||||
}
|
||||
|
||||
struct AVBindSession {
|
||||
AVBindDevice device;
|
||||
AVCaptureSession *refCaptureSession;
|
||||
AVBindMediaProperty properties[MAX_PROPERTIES];
|
||||
};
|
||||
|
||||
|
||||
STATUS AVBindSessionInit(AVBindDevice device, PAVBindSession *ppSessionResult) {
|
||||
STATUS retStatus = STATUS_OK;
|
||||
CHK(ppSessionResult != NULL, STATUS_NULL_ARG);
|
||||
PAVBindSession pSession = malloc(sizeof(AVBindSession));
|
||||
pSession->device = device;
|
||||
pSession->refCaptureSession = NULL;
|
||||
*ppSessionResult = pSession;
|
||||
|
||||
cleanup:
|
||||
return retStatus;
|
||||
}
|
||||
|
||||
STATUS AVBindSessionFree(PAVBindSession *ppSession) {
|
||||
STATUS retStatus = STATUS_OK;
|
||||
CHK(ppSession != NULL, STATUS_NULL_ARG);
|
||||
PAVBindSession pSession = *ppSession;
|
||||
if (pSession->refCaptureSession != NULL) {
|
||||
[pSession->refCaptureSession release];
|
||||
pSession->refCaptureSession = NULL;
|
||||
}
|
||||
free(pSession);
|
||||
*ppSession = NULL;
|
||||
|
||||
cleanup:
|
||||
return retStatus;
|
||||
}
|
||||
|
||||
STATUS AVBindSessionOpen(PAVBindSession pSession,
|
||||
AVBindMediaProperty property,
|
||||
AVBindDataCallback dataCallback,
|
||||
void *pUserData) {
|
||||
STATUS retStatus = STATUS_OK;
|
||||
NSAutoreleasePool *refPool = [[NSAutoreleasePool alloc] init];
|
||||
CHK(pSession != NULL && dataCallback != NULL, STATUS_NULL_ARG);
|
||||
|
||||
AVCaptureDeviceInput *refInput;
|
||||
NSError *refErr = NULL;
|
||||
NSString *refUID = [NSString stringWithUTF8String: pSession->device.uid];
|
||||
AVCaptureDevice *refDevice = [AVCaptureDevice deviceWithUniqueID: refUID];
|
||||
|
||||
refInput = [[AVCaptureDeviceInput alloc] initWithDevice: refDevice error: &refErr];
|
||||
CHK(refErr == NULL, STATUS_DEVICE_INIT_FAILED);
|
||||
|
||||
AVCaptureSession *refCaptureSession = [[AVCaptureSession alloc] init];
|
||||
refCaptureSession.sessionPreset = AVCaptureSessionPresetMedium;
|
||||
[refCaptureSession addInput: refInput];
|
||||
|
||||
if ([refDevice hasMediaType: AVMediaTypeVideo]) {
|
||||
VideoDataDelegate *pDelegate = [[VideoDataDelegate alloc]
|
||||
init: dataCallback
|
||||
withUserData: pUserData];
|
||||
|
||||
AVCaptureVideoDataOutput *pOutput = [[AVCaptureVideoDataOutput alloc] init];
|
||||
FourCharCode fourCC;
|
||||
CHK_STATUS(frameFormatToFourCC(property.frameFormat, &fourCC));
|
||||
|
||||
pOutput.videoSettings = @{
|
||||
(id)kCVPixelBufferWidthKey: @(property.width),
|
||||
(id)kCVPixelBufferHeightKey: @(property.height),
|
||||
(id)kCVPixelBufferPixelFormatTypeKey: @(fourCC),
|
||||
};
|
||||
pOutput.alwaysDiscardsLateVideoFrames = YES;
|
||||
dispatch_queue_t queue =
|
||||
dispatch_queue_create("captureQueue", DISPATCH_QUEUE_SERIAL);
|
||||
[pOutput setSampleBufferDelegate:pDelegate queue:queue];
|
||||
[refCaptureSession addOutput: pOutput];
|
||||
} else {
|
||||
// TODO: implement audio pipeline
|
||||
}
|
||||
|
||||
pSession->refCaptureSession = [refCaptureSession retain];
|
||||
[refCaptureSession startRunning];
|
||||
|
||||
cleanup:
|
||||
[refPool drain];
|
||||
return retStatus;
|
||||
}
|
||||
|
||||
|
||||
STATUS AVBindSessionClose(PAVBindSession pSession) {
|
||||
STATUS retStatus = STATUS_OK;
|
||||
CHK(pSession != NULL, STATUS_NULL_ARG);
|
||||
CHK(pSession->refCaptureSession != NULL, STATUS_OK);
|
||||
|
||||
[pSession->refCaptureSession stopRunning];
|
||||
[pSession->refCaptureSession release];
|
||||
pSession->refCaptureSession = NULL;
|
||||
|
||||
cleanup:
|
||||
return retStatus;
|
||||
}
|
||||
|
||||
STATUS AVBindSessionProperties(PAVBindSession pSession, PAVBindMediaProperty *ppProperties, int *pLen) {
|
||||
STATUS retStatus = STATUS_OK;
|
||||
NSAutoreleasePool *refPool = [[NSAutoreleasePool alloc] init];
|
||||
CHK(pSession != NULL && ppProperties != NULL && pLen != NULL, STATUS_NULL_ARG);
|
||||
|
||||
NSString *refDeviceUID = [NSString stringWithUTF8String: pSession->device.uid];
|
||||
AVCaptureDevice *refDevice = [AVCaptureDevice deviceWithUniqueID: refDeviceUID];
|
||||
FourCharCode fourCC;
|
||||
CMVideoFormatDescriptionRef videoFormat;
|
||||
CMVideoDimensions videoDimensions;
|
||||
|
||||
memset(pSession->properties, 0, sizeof(pSession->properties));
|
||||
PAVBindMediaProperty pProperty = pSession->properties;
|
||||
int len = 0;
|
||||
for (AVCaptureDeviceFormat *refFormat in refDevice.formats) {
|
||||
// TODO: Probably gives a warn to the user
|
||||
if (len >= MAX_PROPERTIES) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ([refFormat.mediaType isEqual:AVMediaTypeVideo]) {
|
||||
fourCC = CMFormatDescriptionGetMediaSubType(refFormat.formatDescription);
|
||||
if (frameFormatFromFourCC(fourCC, &pProperty->frameFormat) != STATUS_OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
videoFormat = (CMVideoFormatDescriptionRef) refFormat.formatDescription;
|
||||
videoDimensions = CMVideoFormatDescriptionGetDimensions(videoFormat);
|
||||
pProperty->height = videoDimensions.height;
|
||||
pProperty->width = videoDimensions.width;
|
||||
} else {
|
||||
// TODO: Get audio properties
|
||||
}
|
||||
|
||||
pProperty++;
|
||||
len++;
|
||||
}
|
||||
|
||||
*ppProperties = pSession->properties;
|
||||
*pLen = len;
|
||||
|
||||
cleanup:
|
||||
|
||||
[refPool drain];
|
||||
return retStatus;
|
||||
}
|
56
pkg/avfoundation/avfoundation_callback_darwin.go
Normal file
56
pkg/avfoundation/avfoundation_callback_darwin.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package avfoundation
|
||||
|
||||
// extern void onData(void*, void*, int);
|
||||
import "C"
|
||||
import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var mu sync.Mutex
|
||||
var nextID handleID
|
||||
|
||||
type dataCb func(data []byte)
|
||||
|
||||
var handles = make(map[handleID]dataCb)
|
||||
|
||||
type handleID int
|
||||
|
||||
//export onData
|
||||
func onData(userData unsafe.Pointer, buf unsafe.Pointer, length C.int) {
|
||||
data := C.GoBytes(buf, length)
|
||||
|
||||
handleNum := (*C.int)(userData)
|
||||
cb, ok := lookup(handleID(*handleNum))
|
||||
if ok {
|
||||
cb(data)
|
||||
}
|
||||
}
|
||||
|
||||
func register(fn dataCb) handleID {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
nextID++
|
||||
for handles[nextID] != nil {
|
||||
nextID++
|
||||
}
|
||||
handles[nextID] = fn
|
||||
|
||||
return nextID
|
||||
}
|
||||
|
||||
func lookup(i handleID) (cb dataCb, ok bool) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
cb, ok = handles[i]
|
||||
return
|
||||
}
|
||||
|
||||
func unregister(i handleID) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
delete(handles, i)
|
||||
}
|
217
pkg/avfoundation/avfoundation_darwin.go
Normal file
217
pkg/avfoundation/avfoundation_darwin.go
Normal file
@@ -0,0 +1,217 @@
|
||||
// Package avfoundation provides AVFoundation binding for Go
|
||||
package avfoundation
|
||||
|
||||
// #cgo CFLAGS: -x objective-c
|
||||
// #cgo LDFLAGS: -framework AVFoundation -framework Foundation -framework CoreMedia -framework CoreVideo
|
||||
// #include "AVFoundationBind/AVFoundationBind.h"
|
||||
// #include "AVFoundationBind/AVFoundationBind.m"
|
||||
// extern void onData(void*, void*, int);
|
||||
// void onDataBridge(void *userData, void *buf, int len) {
|
||||
// onData(userData, buf, len);
|
||||
// }
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/frame"
|
||||
"github.com/pion/mediadevices/pkg/prop"
|
||||
)
|
||||
|
||||
type MediaType C.AVBindMediaType
|
||||
|
||||
const (
|
||||
Video = MediaType(C.AVBindMediaTypeVideo)
|
||||
Audio = MediaType(C.AVBindMediaTypeAudio)
|
||||
)
|
||||
|
||||
// Device represents a metadata that later can be used to retrieve back the
|
||||
// underlying device given by AVFoundation
|
||||
type Device struct {
|
||||
// UID is a unique identifier for a device
|
||||
UID string
|
||||
cDevice C.AVBindDevice
|
||||
}
|
||||
|
||||
func frameFormatToAVBind(f frame.Format) (C.AVBindFrameFormat, bool) {
|
||||
switch f {
|
||||
case frame.FormatI420:
|
||||
return C.AVBindFrameFormatI420, true
|
||||
case frame.FormatNV21:
|
||||
return C.AVBindFrameFormatNV21, true
|
||||
case frame.FormatYUY2:
|
||||
return C.AVBindFrameFormatYUY2, true
|
||||
case frame.FormatUYVY:
|
||||
return C.AVBindFrameFormatUYVY, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
func frameFormatFromAVBind(f C.AVBindFrameFormat) (frame.Format, bool) {
|
||||
switch f {
|
||||
case C.AVBindFrameFormatI420:
|
||||
return frame.FormatI420, true
|
||||
case C.AVBindFrameFormatNV21:
|
||||
return frame.FormatNV21, true
|
||||
case C.AVBindFrameFormatYUY2:
|
||||
return frame.FormatYUY2, true
|
||||
case C.AVBindFrameFormatUYVY:
|
||||
return frame.FormatUYVY, true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
// Devices uses AVFoundation to query a list of devices based on the media type
|
||||
func Devices(mediaType MediaType) ([]Device, error) {
|
||||
var cDevicesPtr C.PAVBindDevice
|
||||
var cDevicesLen C.int
|
||||
|
||||
status := C.AVBindDevices(C.AVBindMediaType(mediaType), &cDevicesPtr, &cDevicesLen)
|
||||
if status != nil {
|
||||
return nil, fmt.Errorf("%s", C.GoString(status))
|
||||
}
|
||||
|
||||
// https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
|
||||
cDevices := (*[1 << 28]C.AVBindDevice)(unsafe.Pointer(cDevicesPtr))[:cDevicesLen:cDevicesLen]
|
||||
devices := make([]Device, cDevicesLen)
|
||||
|
||||
for i := range devices {
|
||||
devices[i].UID = C.GoString(&cDevices[i].uid[0])
|
||||
devices[i].cDevice = cDevices[i]
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
// ReadCloser is a wrapper around the data callback from AVFoundation. The data received from the
|
||||
// the underlying callback can be retrieved by calling Read.
|
||||
type ReadCloser struct {
|
||||
dataChan chan []byte
|
||||
id handleID
|
||||
onClose func()
|
||||
}
|
||||
|
||||
func newReadCloser(onClose func()) *ReadCloser {
|
||||
var rc ReadCloser
|
||||
rc.dataChan = make(chan []byte, 1)
|
||||
rc.onClose = onClose
|
||||
rc.id = register(rc.dataCb)
|
||||
return &rc
|
||||
}
|
||||
|
||||
func (rc *ReadCloser) dataCb(data []byte) {
|
||||
// TODO: add a policy for slow reader
|
||||
rc.dataChan <- data
|
||||
}
|
||||
|
||||
// Read reads raw data, the format is determined by the media type and property:
|
||||
// - For video, each call will return a frame.
|
||||
// - For audio, each call will return a chunk which its size configured by Latency
|
||||
func (rc *ReadCloser) Read() ([]byte, error) {
|
||||
data, ok := <-rc.dataChan
|
||||
if !ok {
|
||||
return nil, io.EOF
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Close closes the capturing session, and no data will flow anymore
|
||||
func (rc *ReadCloser) Close() {
|
||||
if rc.onClose != nil {
|
||||
rc.onClose()
|
||||
}
|
||||
close(rc.dataChan)
|
||||
unregister(rc.id)
|
||||
}
|
||||
|
||||
// Session represents a capturing session.
|
||||
type Session struct {
|
||||
device Device
|
||||
cSession C.PAVBindSession
|
||||
}
|
||||
|
||||
// NewSession creates a new capturing session
|
||||
func NewSession(device Device) (*Session, error) {
|
||||
var session Session
|
||||
|
||||
status := C.AVBindSessionInit(device.cDevice, &session.cSession)
|
||||
if status != nil {
|
||||
return nil, fmt.Errorf("%s", C.GoString(status))
|
||||
}
|
||||
|
||||
session.device = device
|
||||
return &session, nil
|
||||
}
|
||||
|
||||
// Close stops capturing session and frees up resources
|
||||
func (session *Session) Close() error {
|
||||
if session.cSession == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
status := C.AVBindSessionFree(&session.cSession)
|
||||
if status != nil {
|
||||
return fmt.Errorf("%s", C.GoString(status))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Open start capturing session. As soon as it returns successfully, the data will start
|
||||
// flowing. The raw data can be retrieved by using ReadCloser's Read method.
|
||||
func (session *Session) Open(property prop.Media) (*ReadCloser, error) {
|
||||
frameFormat, ok := frameFormatToAVBind(property.FrameFormat)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Unsupported frame format")
|
||||
}
|
||||
|
||||
cProperty := C.AVBindMediaProperty{
|
||||
width: C.int(property.Width),
|
||||
height: C.int(property.Height),
|
||||
frameFormat: frameFormat,
|
||||
}
|
||||
|
||||
rc := newReadCloser(func() {
|
||||
C.AVBindSessionClose(session.cSession)
|
||||
})
|
||||
status := C.AVBindSessionOpen(
|
||||
session.cSession,
|
||||
cProperty,
|
||||
C.AVBindDataCallback(unsafe.Pointer(C.onDataBridge)),
|
||||
unsafe.Pointer(&rc.id),
|
||||
)
|
||||
if status != nil {
|
||||
return nil, fmt.Errorf("%s", C.GoString(status))
|
||||
}
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
// Properties queries a list of properties that device supports
|
||||
func (session *Session) Properties() []prop.Media {
|
||||
var cPropertiesPtr C.PAVBindMediaProperty
|
||||
var cPropertiesLen C.int
|
||||
|
||||
status := C.AVBindSessionProperties(session.cSession, &cPropertiesPtr, &cPropertiesLen)
|
||||
if status != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
|
||||
cProperties := (*[1 << 28]C.AVBindMediaProperty)(unsafe.Pointer(cPropertiesPtr))[:cPropertiesLen:cPropertiesLen]
|
||||
var properties []prop.Media
|
||||
for _, cProperty := range cProperties {
|
||||
frameFormat, ok := frameFormatFromAVBind(cProperty.frameFormat)
|
||||
if ok {
|
||||
properties = append(properties, prop.Media{
|
||||
Video: prop.Video{
|
||||
Width: int(cProperty.width),
|
||||
Height: int(cProperty.height),
|
||||
FrameFormat: frameFormat,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return properties
|
||||
}
|
71
pkg/driver/camera/camera_darwin.go
Normal file
71
pkg/driver/camera/camera_darwin.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package camera
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/pion/mediadevices/pkg/avfoundation"
|
||||
"github.com/pion/mediadevices/pkg/driver"
|
||||
"github.com/pion/mediadevices/pkg/frame"
|
||||
"github.com/pion/mediadevices/pkg/io/video"
|
||||
"github.com/pion/mediadevices/pkg/prop"
|
||||
)
|
||||
|
||||
type camera struct {
|
||||
device avfoundation.Device
|
||||
session *avfoundation.Session
|
||||
}
|
||||
|
||||
func init() {
|
||||
devices, err := avfoundation.Devices(avfoundation.Video)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, device := range devices {
|
||||
cam := newCamera(device)
|
||||
driver.GetManager().Register(cam, driver.Info{
|
||||
Label: device.UID,
|
||||
DeviceType: driver.Camera,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newCamera(device avfoundation.Device) *camera {
|
||||
return &camera{
|
||||
device: device,
|
||||
}
|
||||
}
|
||||
|
||||
func (cam *camera) Open() error {
|
||||
var err error
|
||||
cam.session, err = avfoundation.NewSession(cam.device)
|
||||
return err
|
||||
}
|
||||
|
||||
func (cam *camera) Close() error {
|
||||
return cam.session.Close()
|
||||
}
|
||||
|
||||
func (cam *camera) VideoRecord(property prop.Media) (video.Reader, error) {
|
||||
decoder, err := frame.NewDecoder(property.FrameFormat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rc, err := cam.session.Open(property)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := video.ReaderFunc(func() (image.Image, error) {
|
||||
frame, err := rc.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decoder.Decode(frame, property.Width, property.Height)
|
||||
})
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (cam *camera) Properties() []prop.Media {
|
||||
return cam.session.Properties()
|
||||
}
|
Reference in New Issue
Block a user