├── .gitignore ├── LICENSE ├── README.md ├── VideoToolboxCompression.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── majun.xcuserdatad │ └── xcdebugger │ └── Breakpoints_v2.xcbkptlist ├── VideoToolboxCompression ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── CVPixelBuffer+Extension.swift ├── Info.plist └── ViewController.swift ├── VideoToolboxCompressionTests ├── Info.plist └── VideoToolboxCompressionTests.swift └── VideoToolboxCompressionUITests ├── Info.plist └── VideoToolboxCompressionUITests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 tomisacat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VideoToolbox HEVC Encoder Sample Code 2 | 3 | Sample code that capture and encode video into HEVC (or H.264) with AVFoundation and VideoToolbox. The code is in Swift 4 and tested on XCode 9.2 / iOS 11.2.1 / iPhone X. 4 | 5 | Based on [tomisacat's VideoToolboxCompression project](https://github.com/tomisacat/VideoToolboxCompression). 6 | 7 | Brief instructions: 8 | 1. Build and run on iPhone 7/7 Plus or up. Touch **Click Me** to begin recording. Touch again to finish. 9 | 2. Download the result to Mac: XCode -> Window -> Devices and Simulators -> Select app and click the "gear" icon below -> Download Container. 10 | 3. Among the container files, `tmp/temp.h265` is the raw HEVC data file. 11 | 4. Add a container around the file: `mp4box -add temp.h265 temp.h265.mp4` 12 | 5. Use QuickTime or VLC to play the mp4 file. 13 | 6. For comparison, you could change `H265` to `false` in `ViewController.swift` to do H.264 instead of HEVC encoding. 14 | 15 | -------------------------------------------------------------------------------- /VideoToolboxCompression.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F210EAEB1F3EE212006E9756 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F210EAEA1F3EE212006E9756 /* AppDelegate.swift */; }; 11 | F210EAED1F3EE212006E9756 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F210EAEC1F3EE212006E9756 /* ViewController.swift */; }; 12 | F210EAF01F3EE212006E9756 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F210EAEE1F3EE212006E9756 /* Main.storyboard */; }; 13 | F210EAF21F3EE212006E9756 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F210EAF11F3EE212006E9756 /* Assets.xcassets */; }; 14 | F210EAF51F3EE212006E9756 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F210EAF31F3EE212006E9756 /* LaunchScreen.storyboard */; }; 15 | F210EB001F3EE212006E9756 /* VideoToolboxCompressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F210EAFF1F3EE212006E9756 /* VideoToolboxCompressionTests.swift */; }; 16 | F210EB0B1F3EE212006E9756 /* VideoToolboxCompressionUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F210EB0A1F3EE212006E9756 /* VideoToolboxCompressionUITests.swift */; }; 17 | F210EB191F416083006E9756 /* CVPixelBuffer+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F210EB181F416083006E9756 /* CVPixelBuffer+Extension.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXContainerItemProxy section */ 21 | F210EAFC1F3EE212006E9756 /* PBXContainerItemProxy */ = { 22 | isa = PBXContainerItemProxy; 23 | containerPortal = F210EADF1F3EE212006E9756 /* Project object */; 24 | proxyType = 1; 25 | remoteGlobalIDString = F210EAE61F3EE212006E9756; 26 | remoteInfo = VideoToolboxCompression; 27 | }; 28 | F210EB071F3EE212006E9756 /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = F210EADF1F3EE212006E9756 /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = F210EAE61F3EE212006E9756; 33 | remoteInfo = VideoToolboxCompression; 34 | }; 35 | /* End PBXContainerItemProxy section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | F210EAE71F3EE212006E9756 /* VideoToolboxCompression.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VideoToolboxCompression.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | F210EAEA1F3EE212006E9756 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 40 | F210EAEC1F3EE212006E9756 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 41 | F210EAEF1F3EE212006E9756 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | F210EAF11F3EE212006E9756 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | F210EAF41F3EE212006E9756 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | F210EAF61F3EE212006E9756 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | F210EAFB1F3EE212006E9756 /* VideoToolboxCompressionTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VideoToolboxCompressionTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | F210EAFF1F3EE212006E9756 /* VideoToolboxCompressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoToolboxCompressionTests.swift; sourceTree = ""; }; 47 | F210EB011F3EE212006E9756 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | F210EB061F3EE212006E9756 /* VideoToolboxCompressionUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VideoToolboxCompressionUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | F210EB0A1F3EE212006E9756 /* VideoToolboxCompressionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoToolboxCompressionUITests.swift; sourceTree = ""; }; 50 | F210EB0C1F3EE212006E9756 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | F210EB181F416083006E9756 /* CVPixelBuffer+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CVPixelBuffer+Extension.swift"; sourceTree = ""; }; 52 | /* End PBXFileReference section */ 53 | 54 | /* Begin PBXFrameworksBuildPhase section */ 55 | F210EAE41F3EE212006E9756 /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | F210EAF81F3EE212006E9756 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | F210EB031F3EE212006E9756 /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | /* End PBXFrameworksBuildPhase section */ 77 | 78 | /* Begin PBXGroup section */ 79 | F210EADE1F3EE212006E9756 = { 80 | isa = PBXGroup; 81 | children = ( 82 | F210EAE91F3EE212006E9756 /* VideoToolboxCompression */, 83 | F210EAFE1F3EE212006E9756 /* VideoToolboxCompressionTests */, 84 | F210EB091F3EE212006E9756 /* VideoToolboxCompressionUITests */, 85 | F210EAE81F3EE212006E9756 /* Products */, 86 | ); 87 | sourceTree = ""; 88 | }; 89 | F210EAE81F3EE212006E9756 /* Products */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | F210EAE71F3EE212006E9756 /* VideoToolboxCompression.app */, 93 | F210EAFB1F3EE212006E9756 /* VideoToolboxCompressionTests.xctest */, 94 | F210EB061F3EE212006E9756 /* VideoToolboxCompressionUITests.xctest */, 95 | ); 96 | name = Products; 97 | sourceTree = ""; 98 | }; 99 | F210EAE91F3EE212006E9756 /* VideoToolboxCompression */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | F210EAEA1F3EE212006E9756 /* AppDelegate.swift */, 103 | F210EAEC1F3EE212006E9756 /* ViewController.swift */, 104 | F210EB181F416083006E9756 /* CVPixelBuffer+Extension.swift */, 105 | F210EAEE1F3EE212006E9756 /* Main.storyboard */, 106 | F210EAF11F3EE212006E9756 /* Assets.xcassets */, 107 | F210EAF31F3EE212006E9756 /* LaunchScreen.storyboard */, 108 | F210EAF61F3EE212006E9756 /* Info.plist */, 109 | ); 110 | path = VideoToolboxCompression; 111 | sourceTree = ""; 112 | }; 113 | F210EAFE1F3EE212006E9756 /* VideoToolboxCompressionTests */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | F210EAFF1F3EE212006E9756 /* VideoToolboxCompressionTests.swift */, 117 | F210EB011F3EE212006E9756 /* Info.plist */, 118 | ); 119 | path = VideoToolboxCompressionTests; 120 | sourceTree = ""; 121 | }; 122 | F210EB091F3EE212006E9756 /* VideoToolboxCompressionUITests */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | F210EB0A1F3EE212006E9756 /* VideoToolboxCompressionUITests.swift */, 126 | F210EB0C1F3EE212006E9756 /* Info.plist */, 127 | ); 128 | path = VideoToolboxCompressionUITests; 129 | sourceTree = ""; 130 | }; 131 | /* End PBXGroup section */ 132 | 133 | /* Begin PBXNativeTarget section */ 134 | F210EAE61F3EE212006E9756 /* VideoToolboxCompression */ = { 135 | isa = PBXNativeTarget; 136 | buildConfigurationList = F210EB0F1F3EE212006E9756 /* Build configuration list for PBXNativeTarget "VideoToolboxCompression" */; 137 | buildPhases = ( 138 | F210EAE31F3EE212006E9756 /* Sources */, 139 | F210EAE41F3EE212006E9756 /* Frameworks */, 140 | F210EAE51F3EE212006E9756 /* Resources */, 141 | ); 142 | buildRules = ( 143 | ); 144 | dependencies = ( 145 | ); 146 | name = VideoToolboxCompression; 147 | productName = VideoToolboxCompression; 148 | productReference = F210EAE71F3EE212006E9756 /* VideoToolboxCompression.app */; 149 | productType = "com.apple.product-type.application"; 150 | }; 151 | F210EAFA1F3EE212006E9756 /* VideoToolboxCompressionTests */ = { 152 | isa = PBXNativeTarget; 153 | buildConfigurationList = F210EB121F3EE212006E9756 /* Build configuration list for PBXNativeTarget "VideoToolboxCompressionTests" */; 154 | buildPhases = ( 155 | F210EAF71F3EE212006E9756 /* Sources */, 156 | F210EAF81F3EE212006E9756 /* Frameworks */, 157 | F210EAF91F3EE212006E9756 /* Resources */, 158 | ); 159 | buildRules = ( 160 | ); 161 | dependencies = ( 162 | F210EAFD1F3EE212006E9756 /* PBXTargetDependency */, 163 | ); 164 | name = VideoToolboxCompressionTests; 165 | productName = VideoToolboxCompressionTests; 166 | productReference = F210EAFB1F3EE212006E9756 /* VideoToolboxCompressionTests.xctest */; 167 | productType = "com.apple.product-type.bundle.unit-test"; 168 | }; 169 | F210EB051F3EE212006E9756 /* VideoToolboxCompressionUITests */ = { 170 | isa = PBXNativeTarget; 171 | buildConfigurationList = F210EB151F3EE212006E9756 /* Build configuration list for PBXNativeTarget "VideoToolboxCompressionUITests" */; 172 | buildPhases = ( 173 | F210EB021F3EE212006E9756 /* Sources */, 174 | F210EB031F3EE212006E9756 /* Frameworks */, 175 | F210EB041F3EE212006E9756 /* Resources */, 176 | ); 177 | buildRules = ( 178 | ); 179 | dependencies = ( 180 | F210EB081F3EE212006E9756 /* PBXTargetDependency */, 181 | ); 182 | name = VideoToolboxCompressionUITests; 183 | productName = VideoToolboxCompressionUITests; 184 | productReference = F210EB061F3EE212006E9756 /* VideoToolboxCompressionUITests.xctest */; 185 | productType = "com.apple.product-type.bundle.ui-testing"; 186 | }; 187 | /* End PBXNativeTarget section */ 188 | 189 | /* Begin PBXProject section */ 190 | F210EADF1F3EE212006E9756 /* Project object */ = { 191 | isa = PBXProject; 192 | attributes = { 193 | LastSwiftUpdateCheck = 0900; 194 | LastUpgradeCheck = 0900; 195 | ORGANIZATIONNAME = tomisacat; 196 | TargetAttributes = { 197 | F210EAE61F3EE212006E9756 = { 198 | CreatedOnToolsVersion = 9.0; 199 | }; 200 | F210EAFA1F3EE212006E9756 = { 201 | CreatedOnToolsVersion = 9.0; 202 | TestTargetID = F210EAE61F3EE212006E9756; 203 | }; 204 | F210EB051F3EE212006E9756 = { 205 | CreatedOnToolsVersion = 9.0; 206 | TestTargetID = F210EAE61F3EE212006E9756; 207 | }; 208 | }; 209 | }; 210 | buildConfigurationList = F210EAE21F3EE212006E9756 /* Build configuration list for PBXProject "VideoToolboxCompression" */; 211 | compatibilityVersion = "Xcode 8.0"; 212 | developmentRegion = en; 213 | hasScannedForEncodings = 0; 214 | knownRegions = ( 215 | en, 216 | Base, 217 | ); 218 | mainGroup = F210EADE1F3EE212006E9756; 219 | productRefGroup = F210EAE81F3EE212006E9756 /* Products */; 220 | projectDirPath = ""; 221 | projectRoot = ""; 222 | targets = ( 223 | F210EAE61F3EE212006E9756 /* VideoToolboxCompression */, 224 | F210EAFA1F3EE212006E9756 /* VideoToolboxCompressionTests */, 225 | F210EB051F3EE212006E9756 /* VideoToolboxCompressionUITests */, 226 | ); 227 | }; 228 | /* End PBXProject section */ 229 | 230 | /* Begin PBXResourcesBuildPhase section */ 231 | F210EAE51F3EE212006E9756 /* Resources */ = { 232 | isa = PBXResourcesBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | F210EAF51F3EE212006E9756 /* LaunchScreen.storyboard in Resources */, 236 | F210EAF21F3EE212006E9756 /* Assets.xcassets in Resources */, 237 | F210EAF01F3EE212006E9756 /* Main.storyboard in Resources */, 238 | ); 239 | runOnlyForDeploymentPostprocessing = 0; 240 | }; 241 | F210EAF91F3EE212006E9756 /* Resources */ = { 242 | isa = PBXResourcesBuildPhase; 243 | buildActionMask = 2147483647; 244 | files = ( 245 | ); 246 | runOnlyForDeploymentPostprocessing = 0; 247 | }; 248 | F210EB041F3EE212006E9756 /* Resources */ = { 249 | isa = PBXResourcesBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | ); 253 | runOnlyForDeploymentPostprocessing = 0; 254 | }; 255 | /* End PBXResourcesBuildPhase section */ 256 | 257 | /* Begin PBXSourcesBuildPhase section */ 258 | F210EAE31F3EE212006E9756 /* Sources */ = { 259 | isa = PBXSourcesBuildPhase; 260 | buildActionMask = 2147483647; 261 | files = ( 262 | F210EAED1F3EE212006E9756 /* ViewController.swift in Sources */, 263 | F210EB191F416083006E9756 /* CVPixelBuffer+Extension.swift in Sources */, 264 | F210EAEB1F3EE212006E9756 /* AppDelegate.swift in Sources */, 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | F210EAF71F3EE212006E9756 /* Sources */ = { 269 | isa = PBXSourcesBuildPhase; 270 | buildActionMask = 2147483647; 271 | files = ( 272 | F210EB001F3EE212006E9756 /* VideoToolboxCompressionTests.swift in Sources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | F210EB021F3EE212006E9756 /* Sources */ = { 277 | isa = PBXSourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | F210EB0B1F3EE212006E9756 /* VideoToolboxCompressionUITests.swift in Sources */, 281 | ); 282 | runOnlyForDeploymentPostprocessing = 0; 283 | }; 284 | /* End PBXSourcesBuildPhase section */ 285 | 286 | /* Begin PBXTargetDependency section */ 287 | F210EAFD1F3EE212006E9756 /* PBXTargetDependency */ = { 288 | isa = PBXTargetDependency; 289 | target = F210EAE61F3EE212006E9756 /* VideoToolboxCompression */; 290 | targetProxy = F210EAFC1F3EE212006E9756 /* PBXContainerItemProxy */; 291 | }; 292 | F210EB081F3EE212006E9756 /* PBXTargetDependency */ = { 293 | isa = PBXTargetDependency; 294 | target = F210EAE61F3EE212006E9756 /* VideoToolboxCompression */; 295 | targetProxy = F210EB071F3EE212006E9756 /* PBXContainerItemProxy */; 296 | }; 297 | /* End PBXTargetDependency section */ 298 | 299 | /* Begin PBXVariantGroup section */ 300 | F210EAEE1F3EE212006E9756 /* Main.storyboard */ = { 301 | isa = PBXVariantGroup; 302 | children = ( 303 | F210EAEF1F3EE212006E9756 /* Base */, 304 | ); 305 | name = Main.storyboard; 306 | sourceTree = ""; 307 | }; 308 | F210EAF31F3EE212006E9756 /* LaunchScreen.storyboard */ = { 309 | isa = PBXVariantGroup; 310 | children = ( 311 | F210EAF41F3EE212006E9756 /* Base */, 312 | ); 313 | name = LaunchScreen.storyboard; 314 | sourceTree = ""; 315 | }; 316 | /* End PBXVariantGroup section */ 317 | 318 | /* Begin XCBuildConfiguration section */ 319 | F210EB0D1F3EE212006E9756 /* Debug */ = { 320 | isa = XCBuildConfiguration; 321 | buildSettings = { 322 | ALWAYS_SEARCH_USER_PATHS = NO; 323 | CLANG_ANALYZER_NONNULL = YES; 324 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 325 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 326 | CLANG_CXX_LIBRARY = "libc++"; 327 | CLANG_ENABLE_MODULES = YES; 328 | CLANG_ENABLE_OBJC_ARC = YES; 329 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 330 | CLANG_WARN_BOOL_CONVERSION = YES; 331 | CLANG_WARN_COMMA = YES; 332 | CLANG_WARN_CONSTANT_CONVERSION = YES; 333 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 334 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 335 | CLANG_WARN_EMPTY_BODY = YES; 336 | CLANG_WARN_ENUM_CONVERSION = YES; 337 | CLANG_WARN_INFINITE_RECURSION = YES; 338 | CLANG_WARN_INT_CONVERSION = YES; 339 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 340 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 341 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 342 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 343 | CLANG_WARN_STRICT_PROTOTYPES = YES; 344 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 345 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 346 | CLANG_WARN_UNREACHABLE_CODE = YES; 347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 348 | CODE_SIGN_IDENTITY = "iPhone Developer"; 349 | COPY_PHASE_STRIP = NO; 350 | DEBUG_INFORMATION_FORMAT = dwarf; 351 | ENABLE_STRICT_OBJC_MSGSEND = YES; 352 | ENABLE_TESTABILITY = YES; 353 | GCC_C_LANGUAGE_STANDARD = gnu11; 354 | GCC_DYNAMIC_NO_PIC = NO; 355 | GCC_NO_COMMON_BLOCKS = YES; 356 | GCC_OPTIMIZATION_LEVEL = 0; 357 | GCC_PREPROCESSOR_DEFINITIONS = ( 358 | "DEBUG=1", 359 | "$(inherited)", 360 | ); 361 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 362 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 363 | GCC_WARN_UNDECLARED_SELECTOR = YES; 364 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 365 | GCC_WARN_UNUSED_FUNCTION = YES; 366 | GCC_WARN_UNUSED_VARIABLE = YES; 367 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 368 | MTL_ENABLE_DEBUG_INFO = YES; 369 | ONLY_ACTIVE_ARCH = YES; 370 | SDKROOT = iphoneos; 371 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 372 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 373 | }; 374 | name = Debug; 375 | }; 376 | F210EB0E1F3EE212006E9756 /* Release */ = { 377 | isa = XCBuildConfiguration; 378 | buildSettings = { 379 | ALWAYS_SEARCH_USER_PATHS = NO; 380 | CLANG_ANALYZER_NONNULL = YES; 381 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 382 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 383 | CLANG_CXX_LIBRARY = "libc++"; 384 | CLANG_ENABLE_MODULES = YES; 385 | CLANG_ENABLE_OBJC_ARC = YES; 386 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 387 | CLANG_WARN_BOOL_CONVERSION = YES; 388 | CLANG_WARN_COMMA = YES; 389 | CLANG_WARN_CONSTANT_CONVERSION = YES; 390 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 391 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 392 | CLANG_WARN_EMPTY_BODY = YES; 393 | CLANG_WARN_ENUM_CONVERSION = YES; 394 | CLANG_WARN_INFINITE_RECURSION = YES; 395 | CLANG_WARN_INT_CONVERSION = YES; 396 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 397 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 398 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 399 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 400 | CLANG_WARN_STRICT_PROTOTYPES = YES; 401 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 402 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 403 | CLANG_WARN_UNREACHABLE_CODE = YES; 404 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 405 | CODE_SIGN_IDENTITY = "iPhone Developer"; 406 | COPY_PHASE_STRIP = NO; 407 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 408 | ENABLE_NS_ASSERTIONS = NO; 409 | ENABLE_STRICT_OBJC_MSGSEND = YES; 410 | GCC_C_LANGUAGE_STANDARD = gnu11; 411 | GCC_NO_COMMON_BLOCKS = YES; 412 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 413 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 414 | GCC_WARN_UNDECLARED_SELECTOR = YES; 415 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 416 | GCC_WARN_UNUSED_FUNCTION = YES; 417 | GCC_WARN_UNUSED_VARIABLE = YES; 418 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 419 | MTL_ENABLE_DEBUG_INFO = NO; 420 | SDKROOT = iphoneos; 421 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 422 | VALIDATE_PRODUCT = YES; 423 | }; 424 | name = Release; 425 | }; 426 | F210EB101F3EE212006E9756 /* Debug */ = { 427 | isa = XCBuildConfiguration; 428 | buildSettings = { 429 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 430 | DEVELOPMENT_TEAM = VHJLLQ36QB; 431 | INFOPLIST_FILE = VideoToolboxCompression/Info.plist; 432 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 433 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 434 | PRODUCT_BUNDLE_IDENTIFIER = xyz.tomisacat.VideoToolboxCompression; 435 | PRODUCT_NAME = "$(TARGET_NAME)"; 436 | SWIFT_VERSION = 4.0; 437 | TARGETED_DEVICE_FAMILY = "1,2"; 438 | }; 439 | name = Debug; 440 | }; 441 | F210EB111F3EE212006E9756 /* Release */ = { 442 | isa = XCBuildConfiguration; 443 | buildSettings = { 444 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 445 | DEVELOPMENT_TEAM = VHJLLQ36QB; 446 | INFOPLIST_FILE = VideoToolboxCompression/Info.plist; 447 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 448 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 449 | PRODUCT_BUNDLE_IDENTIFIER = xyz.tomisacat.VideoToolboxCompression; 450 | PRODUCT_NAME = "$(TARGET_NAME)"; 451 | SWIFT_VERSION = 4.0; 452 | TARGETED_DEVICE_FAMILY = "1,2"; 453 | }; 454 | name = Release; 455 | }; 456 | F210EB131F3EE212006E9756 /* Debug */ = { 457 | isa = XCBuildConfiguration; 458 | buildSettings = { 459 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 460 | BUNDLE_LOADER = "$(TEST_HOST)"; 461 | DEVELOPMENT_TEAM = P58JN8D62L; 462 | INFOPLIST_FILE = VideoToolboxCompressionTests/Info.plist; 463 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 464 | PRODUCT_BUNDLE_IDENTIFIER = xyz.tomisacat.VideoToolboxCompressionTests; 465 | PRODUCT_NAME = "$(TARGET_NAME)"; 466 | SWIFT_VERSION = 4.0; 467 | TARGETED_DEVICE_FAMILY = "1,2"; 468 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VideoToolboxCompression.app/VideoToolboxCompression"; 469 | }; 470 | name = Debug; 471 | }; 472 | F210EB141F3EE212006E9756 /* Release */ = { 473 | isa = XCBuildConfiguration; 474 | buildSettings = { 475 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 476 | BUNDLE_LOADER = "$(TEST_HOST)"; 477 | DEVELOPMENT_TEAM = P58JN8D62L; 478 | INFOPLIST_FILE = VideoToolboxCompressionTests/Info.plist; 479 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 480 | PRODUCT_BUNDLE_IDENTIFIER = xyz.tomisacat.VideoToolboxCompressionTests; 481 | PRODUCT_NAME = "$(TARGET_NAME)"; 482 | SWIFT_VERSION = 4.0; 483 | TARGETED_DEVICE_FAMILY = "1,2"; 484 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VideoToolboxCompression.app/VideoToolboxCompression"; 485 | }; 486 | name = Release; 487 | }; 488 | F210EB161F3EE212006E9756 /* Debug */ = { 489 | isa = XCBuildConfiguration; 490 | buildSettings = { 491 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 492 | DEVELOPMENT_TEAM = P58JN8D62L; 493 | INFOPLIST_FILE = VideoToolboxCompressionUITests/Info.plist; 494 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 495 | PRODUCT_BUNDLE_IDENTIFIER = xyz.tomisacat.VideoToolboxCompressionUITests; 496 | PRODUCT_NAME = "$(TARGET_NAME)"; 497 | SWIFT_VERSION = 4.0; 498 | TARGETED_DEVICE_FAMILY = "1,2"; 499 | TEST_TARGET_NAME = VideoToolboxCompression; 500 | }; 501 | name = Debug; 502 | }; 503 | F210EB171F3EE212006E9756 /* Release */ = { 504 | isa = XCBuildConfiguration; 505 | buildSettings = { 506 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 507 | DEVELOPMENT_TEAM = P58JN8D62L; 508 | INFOPLIST_FILE = VideoToolboxCompressionUITests/Info.plist; 509 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 510 | PRODUCT_BUNDLE_IDENTIFIER = xyz.tomisacat.VideoToolboxCompressionUITests; 511 | PRODUCT_NAME = "$(TARGET_NAME)"; 512 | SWIFT_VERSION = 4.0; 513 | TARGETED_DEVICE_FAMILY = "1,2"; 514 | TEST_TARGET_NAME = VideoToolboxCompression; 515 | }; 516 | name = Release; 517 | }; 518 | /* End XCBuildConfiguration section */ 519 | 520 | /* Begin XCConfigurationList section */ 521 | F210EAE21F3EE212006E9756 /* Build configuration list for PBXProject "VideoToolboxCompression" */ = { 522 | isa = XCConfigurationList; 523 | buildConfigurations = ( 524 | F210EB0D1F3EE212006E9756 /* Debug */, 525 | F210EB0E1F3EE212006E9756 /* Release */, 526 | ); 527 | defaultConfigurationIsVisible = 0; 528 | defaultConfigurationName = Release; 529 | }; 530 | F210EB0F1F3EE212006E9756 /* Build configuration list for PBXNativeTarget "VideoToolboxCompression" */ = { 531 | isa = XCConfigurationList; 532 | buildConfigurations = ( 533 | F210EB101F3EE212006E9756 /* Debug */, 534 | F210EB111F3EE212006E9756 /* Release */, 535 | ); 536 | defaultConfigurationIsVisible = 0; 537 | defaultConfigurationName = Release; 538 | }; 539 | F210EB121F3EE212006E9756 /* Build configuration list for PBXNativeTarget "VideoToolboxCompressionTests" */ = { 540 | isa = XCConfigurationList; 541 | buildConfigurations = ( 542 | F210EB131F3EE212006E9756 /* Debug */, 543 | F210EB141F3EE212006E9756 /* Release */, 544 | ); 545 | defaultConfigurationIsVisible = 0; 546 | defaultConfigurationName = Release; 547 | }; 548 | F210EB151F3EE212006E9756 /* Build configuration list for PBXNativeTarget "VideoToolboxCompressionUITests" */ = { 549 | isa = XCConfigurationList; 550 | buildConfigurations = ( 551 | F210EB161F3EE212006E9756 /* Debug */, 552 | F210EB171F3EE212006E9756 /* Release */, 553 | ); 554 | defaultConfigurationIsVisible = 0; 555 | defaultConfigurationName = Release; 556 | }; 557 | /* End XCConfigurationList section */ 558 | }; 559 | rootObject = F210EADF1F3EE212006E9756 /* Project object */; 560 | } 561 | -------------------------------------------------------------------------------- /VideoToolboxCompression.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VideoToolboxCompression.xcodeproj/xcuserdata/majun.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /VideoToolboxCompression/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // VideoToolboxCompression 4 | // 5 | // Created by tomisacat on 12/08/2017. 6 | // Copyright © 2017 tomisacat. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /VideoToolboxCompression/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /VideoToolboxCompression/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /VideoToolboxCompression/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /VideoToolboxCompression/CVPixelBuffer+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CVPixelBuffer+Extension.swift 3 | // VideoToolboxCompression 4 | // 5 | // Created by tomisacat on 14/08/2017. 6 | // Copyright © 2017 tomisacat. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import VideoToolbox 11 | import CoreVideo 12 | 13 | extension CVPixelBuffer { 14 | public enum LockFlag { 15 | case readwrite 16 | case readonly 17 | 18 | func flag() -> CVPixelBufferLockFlags { 19 | switch self { 20 | case .readonly: 21 | return .readOnly 22 | default: 23 | return CVPixelBufferLockFlags.init(rawValue: 0) 24 | } 25 | } 26 | } 27 | 28 | public func lock(_ flag: LockFlag, closure: (() -> Void)?) { 29 | if CVPixelBufferLockBaseAddress(self, flag.flag()) == kCVReturnSuccess { 30 | if let c = closure { 31 | c() 32 | } 33 | } 34 | 35 | CVPixelBufferUnlockBaseAddress(self, flag.flag()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /VideoToolboxCompression/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSCameraUsageDescription 24 | Camera Usage Needed 25 | NSMicrophoneUsageDescription 26 | Microphone Usage Needed 27 | NSPhotoLibraryUsageDescription 28 | Photo Library Usage Needed 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UIStatusBarHidden 38 | 39 | UIStatusBarStyle 40 | UIStatusBarStyleLightContent 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | UISupportedInterfaceOrientations~ipad 48 | 49 | UIInterfaceOrientationPortrait 50 | UIInterfaceOrientationPortraitUpsideDown 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /VideoToolboxCompression/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // VideoToolboxCompression 4 | // 5 | // Created by tomisacat on 12/08/2017. 6 | // Copyright © 2017 tomisacat. All rights reserved. 7 | // 8 | // greenpig 2017.12:增加HEVC的支持,使用方法如下: 9 | // 1. 确认下面的H265变量是true 10 | // 2. 翻译App在iPhone 7以上设备执行,点击Click Me开始录像,再次点击结束 11 | // 3. 在XCode中Window->Devices and Simualtors->选择App点下面齿轮->Download Container 12 | // 4. Container中有tmp/temp.h265文件就是 raw h265 data 13 | // 5. mp4box -add temp.h265 temp.h265.mp4就得到可以用QuickTime播放的HEVC文件了 14 | // 15 | 16 | import UIKit 17 | import AVFoundation 18 | import VideoToolbox 19 | 20 | fileprivate var NALUHeader: [UInt8] = [0, 0, 0, 1] 21 | 22 | let H265 = true 23 | 24 | // 事实上,使用 VideoToolbox 硬编码的用途大多是推流编码后的 NAL Unit 而不是写入到本地一个 H.264 文件 25 | // 如果你想保存到本地,使用 AVAssetWriter 是一个更好的选择,它内部也是会硬编码的 26 | func compressionOutputCallback(outputCallbackRefCon: UnsafeMutableRawPointer?, 27 | sourceFrameRefCon: UnsafeMutableRawPointer?, 28 | status: OSStatus, 29 | infoFlags: VTEncodeInfoFlags, 30 | sampleBuffer: CMSampleBuffer?) -> Swift.Void { 31 | guard status == noErr else { 32 | print("error: \(status)") 33 | return 34 | } 35 | 36 | if infoFlags == .frameDropped { 37 | print("frame dropped") 38 | return 39 | } 40 | 41 | guard let sampleBuffer = sampleBuffer else { 42 | print("sampleBuffer is nil") 43 | return 44 | } 45 | 46 | if CMSampleBufferDataIsReady(sampleBuffer) != true { 47 | print("sampleBuffer data is not ready") 48 | return 49 | } 50 | 51 | // 调试信息 52 | // let desc = CMSampleBufferGetFormatDescription(sampleBuffer) 53 | // let extensions = CMFormatDescriptionGetExtensions(desc!) 54 | // print("extensions: \(extensions!)") 55 | // 56 | // let sampleCount = CMSampleBufferGetNumSamples(sampleBuffer) 57 | // print("sample count: \(sampleCount)") 58 | // 59 | // let dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer)! 60 | // var length: Int = 0 61 | // var dataPointer: UnsafeMutablePointer? 62 | // CMBlockBufferGetDataPointer(dataBuffer, 0, nil, &length, &dataPointer) 63 | // print("length: \(length), dataPointer: \(dataPointer!)") 64 | // 调试信息结束 65 | 66 | let vc: ViewController = Unmanaged.fromOpaque(outputCallbackRefCon!).takeUnretainedValue() 67 | 68 | if let attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true) { 69 | print("attachments: \(attachments)") 70 | 71 | let rawDic: UnsafeRawPointer = CFArrayGetValueAtIndex(attachments, 0) 72 | let dic: CFDictionary = Unmanaged.fromOpaque(rawDic).takeUnretainedValue() 73 | 74 | // if not contains means it's an IDR frame 75 | let keyFrame = !CFDictionaryContainsKey(dic, Unmanaged.passUnretained(kCMSampleAttachmentKey_NotSync).toOpaque()) 76 | if keyFrame { 77 | print("IDR frame") 78 | 79 | // sps 80 | let format = CMSampleBufferGetFormatDescription(sampleBuffer) 81 | var spsSize: Int = 0 82 | var spsCount: Int = 0 83 | var nalHeaderLength: Int32 = 0 84 | var sps: UnsafePointer? 85 | var status: OSStatus 86 | if H265 { 87 | // HEVC 88 | 89 | // HEVC比H264多一个VPS 90 | var vpsSize: Int = 0 91 | var vpsCount: Int = 0 92 | var vps: UnsafePointer? 93 | var ppsSize: Int = 0 94 | var ppsCount: Int = 0 95 | var pps: UnsafePointer? 96 | 97 | status = CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format!, 0, &vps, &vpsSize, &vpsCount, &nalHeaderLength) 98 | if status == noErr { 99 | print("HEVC vps: \(String(describing: vps)), vpsSize: \(vpsSize), vpsCount: \(vpsCount), NAL header length: \(nalHeaderLength)") 100 | status = CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format!, 1, &sps, &spsSize, &spsCount, &nalHeaderLength) 101 | if status == noErr { 102 | print("HEVC sps: \(String(describing: sps)), spsSize: \(spsSize), spsCount: \(spsCount), NAL header length: \(nalHeaderLength)") 103 | status = CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format!, 2, &pps, &ppsSize, &ppsCount, &nalHeaderLength) 104 | if status == noErr { 105 | print("HEVC pps: \(String(describing: pps)), ppsSize: \(ppsSize), ppsCount: \(ppsCount), NAL header length: \(nalHeaderLength)") 106 | 107 | let vpsData: NSData = NSData(bytes: vps, length: vpsSize) 108 | let spsData: NSData = NSData(bytes: sps, length: spsSize) 109 | let ppsData: NSData = NSData(bytes: pps, length: ppsSize) 110 | 111 | vc.handle(sps: spsData, pps: ppsData, vps: vpsData) 112 | 113 | } 114 | } 115 | 116 | } 117 | 118 | } else { 119 | // H.264 120 | if CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format!, 121 | 0, 122 | &sps, 123 | &spsSize, 124 | &spsCount, 125 | &nalHeaderLength) == noErr { 126 | print("sps: \(String(describing: sps)), spsSize: \(spsSize), spsCount: \(spsCount), NAL header length: \(nalHeaderLength)") 127 | 128 | // pps 129 | var ppsSize: Int = 0 130 | var ppsCount: Int = 0 131 | var pps: UnsafePointer? 132 | 133 | if CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format!, 134 | 1, 135 | &pps, 136 | &ppsSize, 137 | &ppsCount, 138 | &nalHeaderLength) == noErr { 139 | print("sps: \(String(describing: pps)), spsSize: \(ppsSize), spsCount: \(ppsCount), NAL header length: \(nalHeaderLength)") 140 | 141 | let spsData: NSData = NSData(bytes: sps, length: spsSize) 142 | let ppsData: NSData = NSData(bytes: pps, length: ppsSize) 143 | 144 | // save sps/pps to file 145 | // NOTE: 事实上,大多数情况下 sps/pps 不变/变化不大 或者 变化对视频数据产生的影响很小, 146 | // 因此,多数情况下你都可以只在文件头写入或视频流开头传输 sps/pps 数据 147 | vc.handle(sps: spsData, pps: ppsData) 148 | } 149 | } 150 | } 151 | } // end of handle sps/pps 152 | 153 | // handle frame data 154 | guard let dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer) else { 155 | return 156 | } 157 | 158 | var lengthAtOffset: Int = 0 159 | var totalLength: Int = 0 160 | var dataPointer: UnsafeMutablePointer? 161 | if CMBlockBufferGetDataPointer(dataBuffer, 0, &lengthAtOffset, &totalLength, &dataPointer) == noErr { 162 | var bufferOffset: Int = 0 163 | let AVCCHeaderLength = 4 164 | 165 | while bufferOffset < (totalLength - AVCCHeaderLength) { 166 | var NALUnitLength: UInt32 = 0 167 | // first four character is NALUnit length 168 | memcpy(&NALUnitLength, dataPointer?.advanced(by: bufferOffset), AVCCHeaderLength) 169 | 170 | // big endian to host endian. in iOS it's little endian 171 | NALUnitLength = CFSwapInt32BigToHost(NALUnitLength) 172 | 173 | let data: NSData = NSData(bytes: dataPointer?.advanced(by: bufferOffset + AVCCHeaderLength), length: Int(NALUnitLength)) 174 | vc.encode(data: data, isKeyFrame: keyFrame) 175 | 176 | // move forward to the next NAL Unit 177 | bufferOffset += Int(AVCCHeaderLength) 178 | bufferOffset += Int(NALUnitLength) 179 | } 180 | } 181 | } 182 | } 183 | 184 | class ViewController: UIViewController { 185 | 186 | let captureSession = AVCaptureSession() 187 | let captureQueue = DispatchQueue(label: "videotoolbox.compression.capture") 188 | let compressionQueue = DispatchQueue(label: "videotoolbox.compression.compression") 189 | lazy var preview: AVCaptureVideoPreviewLayer = { 190 | let preview = AVCaptureVideoPreviewLayer(session: self.captureSession) 191 | preview.videoGravity = .resizeAspectFill 192 | view.layer.addSublayer(preview) 193 | 194 | return preview 195 | }() 196 | 197 | var compressionSession: VTCompressionSession? 198 | var fileHandler: FileHandle? 199 | var isCapturing: Bool = false 200 | 201 | override func viewDidLoad() { 202 | super.viewDidLoad() 203 | 204 | let path = NSTemporaryDirectory() + (H265 ? "/temp.h265" : "/temp.h264") 205 | try? FileManager.default.removeItem(atPath: path) 206 | if FileManager.default.createFile(atPath: path, contents: nil, attributes: nil) { 207 | fileHandler = FileHandle(forWritingAtPath: path) 208 | } 209 | 210 | let device = AVCaptureDevice.default(for: .video)! 211 | let input = try! AVCaptureDeviceInput(device: device) 212 | if captureSession.canAddInput(input) { 213 | captureSession.addInput(input) 214 | } 215 | captureSession.sessionPreset = .high 216 | let output = AVCaptureVideoDataOutput() 217 | // YUV 420v 218 | output.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] 219 | output.setSampleBufferDelegate(self, queue: captureQueue) 220 | if captureSession.canAddOutput(output) { 221 | captureSession.addOutput(output) 222 | } 223 | 224 | // not a good method 225 | if let connection = output.connection(with: .video) { 226 | if connection.isVideoOrientationSupported { 227 | connection.videoOrientation = AVCaptureVideoOrientation(rawValue: UIApplication.shared.statusBarOrientation.rawValue)! 228 | } 229 | } 230 | 231 | captureSession.startRunning() 232 | } 233 | 234 | override func viewDidLayoutSubviews() { 235 | preview.frame = view.bounds 236 | 237 | let button = UIButton(type: .roundedRect) 238 | button.setTitle("Click Me", for: .normal) 239 | button.backgroundColor = .red 240 | button.addTarget(self, action: #selector(startOrNot), for: .touchUpInside) 241 | button.frame = CGRect(x: 100, y: 200, width: 100, height: 40) 242 | 243 | view.addSubview(button) 244 | } 245 | } 246 | 247 | extension ViewController { 248 | @objc func startOrNot() { 249 | if isCapturing { 250 | stopCapture() 251 | } else { 252 | startCapture() 253 | } 254 | } 255 | 256 | func startCapture() { 257 | isCapturing = true 258 | } 259 | 260 | func stopCapture() { 261 | isCapturing = false 262 | 263 | guard let compressionSession = compressionSession else { 264 | return 265 | } 266 | 267 | VTCompressionSessionCompleteFrames(compressionSession, kCMTimeInvalid) 268 | VTCompressionSessionInvalidate(compressionSession) 269 | self.compressionSession = nil 270 | } 271 | } 272 | 273 | extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate { 274 | func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { 275 | guard let pixelbuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { 276 | return 277 | } 278 | 279 | // if CVPixelBufferIsPlanar(pixelbuffer) { 280 | // print("planar: \(CVPixelBufferGetPixelFormatType(pixelbuffer))") 281 | // } 282 | // 283 | // var desc: CMFormatDescription? 284 | // CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, pixelbuffer, &desc) 285 | // let extensions = CMFormatDescriptionGetExtensions(desc!) 286 | // print("extensions: \(extensions!)") 287 | 288 | if compressionSession == nil { 289 | let width = CVPixelBufferGetWidth(pixelbuffer) 290 | let height = CVPixelBufferGetHeight(pixelbuffer) 291 | 292 | print("width: \(width), height: \(height)") 293 | 294 | let status = VTCompressionSessionCreate(kCFAllocatorDefault, 295 | Int32(width), 296 | Int32(height), 297 | H265 ? kCMVideoCodecType_HEVC : kCMVideoCodecType_H264, 298 | nil, nil, nil, 299 | compressionOutputCallback, 300 | UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()), 301 | &compressionSession) 302 | 303 | guard let c = compressionSession else { 304 | print("Error creating compression session: \(status)") 305 | return 306 | } 307 | 308 | // set profile to Main 309 | if H265 { 310 | VTSessionSetProperty(c, kVTCompressionPropertyKey_ProfileLevel, 311 | kVTProfileLevel_HEVC_Main_AutoLevel) 312 | } else { 313 | VTSessionSetProperty(c, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Main_AutoLevel) 314 | } 315 | // capture from camera, so it's real time 316 | VTSessionSetProperty(c, kVTCompressionPropertyKey_RealTime, true as CFTypeRef) 317 | // 关键帧间隔 318 | VTSessionSetProperty(c, kVTCompressionPropertyKey_MaxKeyFrameInterval, 10 as CFTypeRef) 319 | // 比特率和速率 320 | VTSessionSetProperty(c, kVTCompressionPropertyKey_AverageBitRate, width * height * 2 * 32 as CFTypeRef) 321 | VTSessionSetProperty(c, kVTCompressionPropertyKey_DataRateLimits, [width * height * 2 * 4, 1] as CFArray) 322 | 323 | VTCompressionSessionPrepareToEncodeFrames(c) 324 | } 325 | 326 | guard let c = compressionSession else { 327 | return 328 | } 329 | 330 | guard isCapturing else { 331 | return 332 | } 333 | 334 | compressionQueue.sync { 335 | pixelbuffer.lock(.readwrite) { 336 | let presentationTimestamp = CMSampleBufferGetOutputPresentationTimeStamp(sampleBuffer) 337 | let duration = CMSampleBufferGetOutputDuration(sampleBuffer) 338 | VTCompressionSessionEncodeFrame(c, pixelbuffer, presentationTimestamp, duration, nil, nil, nil) 339 | } 340 | } 341 | } 342 | 343 | func handle(sps: NSData, pps: NSData, vps: NSData? = nil) { 344 | guard let fh = fileHandler else { 345 | return 346 | } 347 | 348 | let headerData: NSData = NSData(bytes: NALUHeader, length: NALUHeader.count) 349 | if let v = vps { 350 | print("Got VPS data: \(v.length) bytes") 351 | fh.write(headerData as Data) 352 | fh.write(v as Data) 353 | } 354 | 355 | fh.write(headerData as Data) 356 | fh.write(sps as Data) 357 | fh.write(headerData as Data) 358 | fh.write(pps as Data) 359 | } 360 | 361 | func encode(data: NSData, isKeyFrame: Bool) { 362 | guard let fh = fileHandler else { 363 | return 364 | } 365 | let headerData: NSData = NSData(bytes: NALUHeader, length: NALUHeader.count) 366 | fh.write(headerData as Data) 367 | fh.write(data as Data) 368 | } 369 | } 370 | 371 | -------------------------------------------------------------------------------- /VideoToolboxCompressionTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /VideoToolboxCompressionTests/VideoToolboxCompressionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoToolboxCompressionTests.swift 3 | // VideoToolboxCompressionTests 4 | // 5 | // Created by tomisacat on 12/08/2017. 6 | // Copyright © 2017 tomisacat. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import VideoToolboxCompression 11 | 12 | class VideoToolboxCompressionTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /VideoToolboxCompressionUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /VideoToolboxCompressionUITests/VideoToolboxCompressionUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoToolboxCompressionUITests.swift 3 | // VideoToolboxCompressionUITests 4 | // 5 | // Created by tomisacat on 12/08/2017. 6 | // Copyright © 2017 tomisacat. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class VideoToolboxCompressionUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | --------------------------------------------------------------------------------