├── .gitignore ├── README.md ├── WCLRecordVideo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── wangchonglei.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── WCLRecordVideo.xcscheme │ └── xcschememanagement.plist ├── WCLRecordVideo ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── changeCamera.imageset │ │ ├── 0-前后摄像头置换按钮@2x.png │ │ ├── 0-前后摄像头置换按钮@3x.png │ │ └── Contents.json │ ├── closeVideo#.imageset │ │ ├── 0-关闭按钮-点击态@2x.png │ │ ├── 0-关闭按钮-点击态@3x.png │ │ └── Contents.json │ ├── closeVideo.imageset │ │ ├── 0-关闭按钮-正常态@2x.png │ │ ├── 0-关闭按钮-正常态@3x.png │ │ └── Contents.json │ ├── flashlightOff.imageset │ │ ├── 0-闪光灯关@2x.png │ │ ├── 0-闪光灯关@3x.png │ │ └── Contents.json │ ├── flashlightOn.imageset │ │ ├── 0-闪光灯开@2x.png │ │ ├── 0-闪光灯开@3x.png │ │ └── Contents.json │ ├── locationVideo.imageset │ │ ├── 0-本地视频@2x.png │ │ ├── 0-本地视频@3x.png │ │ └── Contents.json │ ├── publishPlayVideo.imageset │ │ ├── 3-播放按钮@2x.png │ │ ├── 3-播放按钮@3x.png │ │ └── Contents.json │ ├── publishPlayVideoBig.imageset │ │ ├── 5-播放按钮@2x.png │ │ ├── 5-播放按钮@3x.png │ │ └── Contents.json │ ├── videoLink.imageset │ │ ├── 0-链接@2x.png │ │ ├── 0-链接@3x.png │ │ └── Contents.json │ ├── videoNext#.imageset │ │ ├── 0-下一步按钮-点击态@2x.png │ │ ├── 0-下一步按钮-点击态@3x.png │ │ └── Contents.json │ ├── videoNext.imageset │ │ ├── 0-下一步按钮-正常态@2x.png │ │ ├── 0-下一步按钮-正常态@3x.png │ │ └── Contents.json │ ├── videoPause.imageset │ │ ├── 2-录制暂停按钮@2x.png │ │ ├── 2-录制暂停按钮@3x.png │ │ └── Contents.json │ └── videoRecord.imageset │ │ ├── 0-录制按钮@2x.png │ │ ├── 0-录制按钮@3x.png │ │ └── Contents.json ├── Base.lproj │ └── Main.storyboard ├── Info.plist ├── Launch Screen.storyboard ├── WCLRecordProgressView │ ├── WCLRecordProgressView.h │ └── WCLRecordProgressView.m ├── WCLRecordVideo │ ├── WCLRecordEncoder │ │ ├── WCLRecordEncoder.h │ │ └── WCLRecordEncoder.m │ └── WCLRecordEngine │ │ ├── WCLRecordEngine.h │ │ └── WCLRecordEngine.m ├── WCLRecordVideoVC.h ├── WCLRecordVideoVC.m └── main.m └── wcl.gif /.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 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | *.DS_Store 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 52 | 53 | fastlane/report.xml 54 | fastlane/screenshots 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### iOS开发进阶 - 用AVFoundation自定义视频录制功能 2 | ### 具体讲解和介绍可以去我的[CSDN博客](http://blog.csdn.net/wang631106979/article/details/51498009)上去阅读.(因为用到摄像头和麦克风,必须的用真机测试) 3 | ![效果图](http://img.blog.csdn.net/20160525152423044) 4 | -------------------------------------------------------------------------------- /WCLRecordVideo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9798598B1CF45DE7009CB223 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9798598A1CF45DE7009CB223 /* main.m */; }; 11 | 9798598E1CF45DE7009CB223 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9798598D1CF45DE7009CB223 /* AppDelegate.m */; }; 12 | 979859941CF45DE7009CB223 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 979859921CF45DE7009CB223 /* Main.storyboard */; }; 13 | 979859961CF45DE7009CB223 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 979859951CF45DE7009CB223 /* Assets.xcassets */; }; 14 | 979859A71CF45E01009CB223 /* WCLRecordEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 979859A31CF45E01009CB223 /* WCLRecordEncoder.m */; }; 15 | 979859A81CF45E01009CB223 /* WCLRecordEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 979859A61CF45E01009CB223 /* WCLRecordEngine.m */; }; 16 | 979859AC1CF46950009CB223 /* WCLRecordProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 979859AB1CF46950009CB223 /* WCLRecordProgressView.m */; }; 17 | 979859AF1CF469F2009CB223 /* WCLRecordVideoVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 979859AE1CF469F2009CB223 /* WCLRecordVideoVC.m */; }; 18 | 979859B11CF46B2F009CB223 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 979859B01CF46B2F009CB223 /* MobileCoreServices.framework */; }; 19 | 979859B31CF46BF9009CB223 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 979859B21CF46BF9009CB223 /* Launch Screen.storyboard */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 979859861CF45DE7009CB223 /* WCLRecordVideo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WCLRecordVideo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 9798598A1CF45DE7009CB223 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 25 | 9798598C1CF45DE7009CB223 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 26 | 9798598D1CF45DE7009CB223 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 27 | 979859931CF45DE7009CB223 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 28 | 979859951CF45DE7009CB223 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 29 | 9798599A1CF45DE7009CB223 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | 979859A21CF45E01009CB223 /* WCLRecordEncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WCLRecordEncoder.h; sourceTree = ""; }; 31 | 979859A31CF45E01009CB223 /* WCLRecordEncoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WCLRecordEncoder.m; sourceTree = ""; }; 32 | 979859A51CF45E01009CB223 /* WCLRecordEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WCLRecordEngine.h; sourceTree = ""; }; 33 | 979859A61CF45E01009CB223 /* WCLRecordEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WCLRecordEngine.m; sourceTree = ""; }; 34 | 979859AA1CF46950009CB223 /* WCLRecordProgressView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WCLRecordProgressView.h; sourceTree = ""; }; 35 | 979859AB1CF46950009CB223 /* WCLRecordProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WCLRecordProgressView.m; sourceTree = ""; }; 36 | 979859AD1CF469F2009CB223 /* WCLRecordVideoVC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WCLRecordVideoVC.h; sourceTree = ""; }; 37 | 979859AE1CF469F2009CB223 /* WCLRecordVideoVC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WCLRecordVideoVC.m; sourceTree = ""; }; 38 | 979859B01CF46B2F009CB223 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 39 | 979859B21CF46BF9009CB223 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; 40 | /* End PBXFileReference section */ 41 | 42 | /* Begin PBXFrameworksBuildPhase section */ 43 | 979859831CF45DE7009CB223 /* Frameworks */ = { 44 | isa = PBXFrameworksBuildPhase; 45 | buildActionMask = 2147483647; 46 | files = ( 47 | 979859B11CF46B2F009CB223 /* MobileCoreServices.framework in Frameworks */, 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | /* End PBXFrameworksBuildPhase section */ 52 | 53 | /* Begin PBXGroup section */ 54 | 9798597D1CF45DE7009CB223 = { 55 | isa = PBXGroup; 56 | children = ( 57 | 979859B01CF46B2F009CB223 /* MobileCoreServices.framework */, 58 | 979859881CF45DE7009CB223 /* WCLRecordVideo */, 59 | 979859871CF45DE7009CB223 /* Products */, 60 | ); 61 | sourceTree = ""; 62 | }; 63 | 979859871CF45DE7009CB223 /* Products */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 979859861CF45DE7009CB223 /* WCLRecordVideo.app */, 67 | ); 68 | name = Products; 69 | sourceTree = ""; 70 | }; 71 | 979859881CF45DE7009CB223 /* WCLRecordVideo */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 979859A91CF46950009CB223 /* WCLRecordProgressView */, 75 | 979859A01CF45E01009CB223 /* WCLRecordVideo */, 76 | 9798598C1CF45DE7009CB223 /* AppDelegate.h */, 77 | 9798598D1CF45DE7009CB223 /* AppDelegate.m */, 78 | 979859AD1CF469F2009CB223 /* WCLRecordVideoVC.h */, 79 | 979859AE1CF469F2009CB223 /* WCLRecordVideoVC.m */, 80 | 979859921CF45DE7009CB223 /* Main.storyboard */, 81 | 979859951CF45DE7009CB223 /* Assets.xcassets */, 82 | 9798599A1CF45DE7009CB223 /* Info.plist */, 83 | 979859891CF45DE7009CB223 /* Supporting Files */, 84 | 979859B21CF46BF9009CB223 /* Launch Screen.storyboard */, 85 | ); 86 | path = WCLRecordVideo; 87 | sourceTree = ""; 88 | }; 89 | 979859891CF45DE7009CB223 /* Supporting Files */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 9798598A1CF45DE7009CB223 /* main.m */, 93 | ); 94 | name = "Supporting Files"; 95 | sourceTree = ""; 96 | }; 97 | 979859A01CF45E01009CB223 /* WCLRecordVideo */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 979859A11CF45E01009CB223 /* WCLRecordEncoder */, 101 | 979859A41CF45E01009CB223 /* WCLRecordEngine */, 102 | ); 103 | path = WCLRecordVideo; 104 | sourceTree = ""; 105 | }; 106 | 979859A11CF45E01009CB223 /* WCLRecordEncoder */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 979859A21CF45E01009CB223 /* WCLRecordEncoder.h */, 110 | 979859A31CF45E01009CB223 /* WCLRecordEncoder.m */, 111 | ); 112 | path = WCLRecordEncoder; 113 | sourceTree = ""; 114 | }; 115 | 979859A41CF45E01009CB223 /* WCLRecordEngine */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 979859A51CF45E01009CB223 /* WCLRecordEngine.h */, 119 | 979859A61CF45E01009CB223 /* WCLRecordEngine.m */, 120 | ); 121 | path = WCLRecordEngine; 122 | sourceTree = ""; 123 | }; 124 | 979859A91CF46950009CB223 /* WCLRecordProgressView */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 979859AA1CF46950009CB223 /* WCLRecordProgressView.h */, 128 | 979859AB1CF46950009CB223 /* WCLRecordProgressView.m */, 129 | ); 130 | path = WCLRecordProgressView; 131 | sourceTree = ""; 132 | }; 133 | /* End PBXGroup section */ 134 | 135 | /* Begin PBXNativeTarget section */ 136 | 979859851CF45DE7009CB223 /* WCLRecordVideo */ = { 137 | isa = PBXNativeTarget; 138 | buildConfigurationList = 9798599D1CF45DE7009CB223 /* Build configuration list for PBXNativeTarget "WCLRecordVideo" */; 139 | buildPhases = ( 140 | 979859821CF45DE7009CB223 /* Sources */, 141 | 979859831CF45DE7009CB223 /* Frameworks */, 142 | 979859841CF45DE7009CB223 /* Resources */, 143 | ); 144 | buildRules = ( 145 | ); 146 | dependencies = ( 147 | ); 148 | name = WCLRecordVideo; 149 | productName = WCLRecordVideo; 150 | productReference = 979859861CF45DE7009CB223 /* WCLRecordVideo.app */; 151 | productType = "com.apple.product-type.application"; 152 | }; 153 | /* End PBXNativeTarget section */ 154 | 155 | /* Begin PBXProject section */ 156 | 9798597E1CF45DE7009CB223 /* Project object */ = { 157 | isa = PBXProject; 158 | attributes = { 159 | LastUpgradeCheck = 0800; 160 | ORGANIZATIONNAME = "王崇磊"; 161 | TargetAttributes = { 162 | 979859851CF45DE7009CB223 = { 163 | CreatedOnToolsVersion = 7.3; 164 | DevelopmentTeam = 464B6RYLBK; 165 | }; 166 | }; 167 | }; 168 | buildConfigurationList = 979859811CF45DE7009CB223 /* Build configuration list for PBXProject "WCLRecordVideo" */; 169 | compatibilityVersion = "Xcode 3.2"; 170 | developmentRegion = English; 171 | hasScannedForEncodings = 0; 172 | knownRegions = ( 173 | en, 174 | Base, 175 | ); 176 | mainGroup = 9798597D1CF45DE7009CB223; 177 | productRefGroup = 979859871CF45DE7009CB223 /* Products */; 178 | projectDirPath = ""; 179 | projectRoot = ""; 180 | targets = ( 181 | 979859851CF45DE7009CB223 /* WCLRecordVideo */, 182 | ); 183 | }; 184 | /* End PBXProject section */ 185 | 186 | /* Begin PBXResourcesBuildPhase section */ 187 | 979859841CF45DE7009CB223 /* Resources */ = { 188 | isa = PBXResourcesBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | 979859B31CF46BF9009CB223 /* Launch Screen.storyboard in Resources */, 192 | 979859961CF45DE7009CB223 /* Assets.xcassets in Resources */, 193 | 979859941CF45DE7009CB223 /* Main.storyboard in Resources */, 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXResourcesBuildPhase section */ 198 | 199 | /* Begin PBXSourcesBuildPhase section */ 200 | 979859821CF45DE7009CB223 /* Sources */ = { 201 | isa = PBXSourcesBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | 979859AC1CF46950009CB223 /* WCLRecordProgressView.m in Sources */, 205 | 9798598E1CF45DE7009CB223 /* AppDelegate.m in Sources */, 206 | 979859A81CF45E01009CB223 /* WCLRecordEngine.m in Sources */, 207 | 979859A71CF45E01009CB223 /* WCLRecordEncoder.m in Sources */, 208 | 979859AF1CF469F2009CB223 /* WCLRecordVideoVC.m in Sources */, 209 | 9798598B1CF45DE7009CB223 /* main.m in Sources */, 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | }; 213 | /* End PBXSourcesBuildPhase section */ 214 | 215 | /* Begin PBXVariantGroup section */ 216 | 979859921CF45DE7009CB223 /* Main.storyboard */ = { 217 | isa = PBXVariantGroup; 218 | children = ( 219 | 979859931CF45DE7009CB223 /* Base */, 220 | ); 221 | name = Main.storyboard; 222 | sourceTree = ""; 223 | }; 224 | /* End PBXVariantGroup section */ 225 | 226 | /* Begin XCBuildConfiguration section */ 227 | 9798599B1CF45DE7009CB223 /* Debug */ = { 228 | isa = XCBuildConfiguration; 229 | buildSettings = { 230 | ALWAYS_SEARCH_USER_PATHS = NO; 231 | CLANG_ANALYZER_NONNULL = YES; 232 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 233 | CLANG_CXX_LIBRARY = "libc++"; 234 | CLANG_ENABLE_MODULES = YES; 235 | CLANG_ENABLE_OBJC_ARC = YES; 236 | CLANG_WARN_BOOL_CONVERSION = YES; 237 | CLANG_WARN_CONSTANT_CONVERSION = YES; 238 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 239 | CLANG_WARN_EMPTY_BODY = YES; 240 | CLANG_WARN_ENUM_CONVERSION = YES; 241 | CLANG_WARN_INFINITE_RECURSION = YES; 242 | CLANG_WARN_INT_CONVERSION = YES; 243 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 244 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 245 | CLANG_WARN_UNREACHABLE_CODE = YES; 246 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 247 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 248 | COPY_PHASE_STRIP = NO; 249 | DEBUG_INFORMATION_FORMAT = dwarf; 250 | ENABLE_STRICT_OBJC_MSGSEND = YES; 251 | ENABLE_TESTABILITY = YES; 252 | GCC_C_LANGUAGE_STANDARD = gnu99; 253 | GCC_DYNAMIC_NO_PIC = NO; 254 | GCC_NO_COMMON_BLOCKS = YES; 255 | GCC_OPTIMIZATION_LEVEL = 0; 256 | GCC_PREPROCESSOR_DEFINITIONS = ( 257 | "DEBUG=1", 258 | "$(inherited)", 259 | ); 260 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 261 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 262 | GCC_WARN_UNDECLARED_SELECTOR = YES; 263 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 264 | GCC_WARN_UNUSED_FUNCTION = YES; 265 | GCC_WARN_UNUSED_VARIABLE = YES; 266 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 267 | MTL_ENABLE_DEBUG_INFO = YES; 268 | ONLY_ACTIVE_ARCH = YES; 269 | SDKROOT = iphoneos; 270 | TARGETED_DEVICE_FAMILY = "1,2"; 271 | }; 272 | name = Debug; 273 | }; 274 | 9798599C1CF45DE7009CB223 /* Release */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | ALWAYS_SEARCH_USER_PATHS = NO; 278 | CLANG_ANALYZER_NONNULL = YES; 279 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 280 | CLANG_CXX_LIBRARY = "libc++"; 281 | CLANG_ENABLE_MODULES = YES; 282 | CLANG_ENABLE_OBJC_ARC = YES; 283 | CLANG_WARN_BOOL_CONVERSION = YES; 284 | CLANG_WARN_CONSTANT_CONVERSION = YES; 285 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 286 | CLANG_WARN_EMPTY_BODY = YES; 287 | CLANG_WARN_ENUM_CONVERSION = YES; 288 | CLANG_WARN_INFINITE_RECURSION = YES; 289 | CLANG_WARN_INT_CONVERSION = YES; 290 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 291 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 292 | CLANG_WARN_UNREACHABLE_CODE = YES; 293 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 294 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 295 | COPY_PHASE_STRIP = NO; 296 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 297 | ENABLE_NS_ASSERTIONS = NO; 298 | ENABLE_STRICT_OBJC_MSGSEND = YES; 299 | GCC_C_LANGUAGE_STANDARD = gnu99; 300 | GCC_NO_COMMON_BLOCKS = YES; 301 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 302 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 303 | GCC_WARN_UNDECLARED_SELECTOR = YES; 304 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 305 | GCC_WARN_UNUSED_FUNCTION = YES; 306 | GCC_WARN_UNUSED_VARIABLE = YES; 307 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 308 | MTL_ENABLE_DEBUG_INFO = NO; 309 | SDKROOT = iphoneos; 310 | TARGETED_DEVICE_FAMILY = "1,2"; 311 | VALIDATE_PRODUCT = YES; 312 | }; 313 | name = Release; 314 | }; 315 | 9798599E1CF45DE7009CB223 /* Debug */ = { 316 | isa = XCBuildConfiguration; 317 | buildSettings = { 318 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 319 | CODE_SIGN_IDENTITY = "iPhone Developer"; 320 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 321 | DEVELOPMENT_TEAM = 464B6RYLBK; 322 | INFOPLIST_FILE = WCLRecordVideo/Info.plist; 323 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 324 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 325 | PRODUCT_BUNDLE_IDENTIFIER = IMWCL.WCLRecordVideo.com; 326 | PRODUCT_NAME = "$(TARGET_NAME)"; 327 | PROVISIONING_PROFILE = ""; 328 | }; 329 | name = Debug; 330 | }; 331 | 9798599F1CF45DE7009CB223 /* Release */ = { 332 | isa = XCBuildConfiguration; 333 | buildSettings = { 334 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 335 | CODE_SIGN_IDENTITY = "iPhone Developer"; 336 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 337 | DEVELOPMENT_TEAM = 464B6RYLBK; 338 | INFOPLIST_FILE = WCLRecordVideo/Info.plist; 339 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 340 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 341 | PRODUCT_BUNDLE_IDENTIFIER = IMWCL.WCLRecordVideo.com; 342 | PRODUCT_NAME = "$(TARGET_NAME)"; 343 | PROVISIONING_PROFILE = ""; 344 | }; 345 | name = Release; 346 | }; 347 | /* End XCBuildConfiguration section */ 348 | 349 | /* Begin XCConfigurationList section */ 350 | 979859811CF45DE7009CB223 /* Build configuration list for PBXProject "WCLRecordVideo" */ = { 351 | isa = XCConfigurationList; 352 | buildConfigurations = ( 353 | 9798599B1CF45DE7009CB223 /* Debug */, 354 | 9798599C1CF45DE7009CB223 /* Release */, 355 | ); 356 | defaultConfigurationIsVisible = 0; 357 | defaultConfigurationName = Release; 358 | }; 359 | 9798599D1CF45DE7009CB223 /* Build configuration list for PBXNativeTarget "WCLRecordVideo" */ = { 360 | isa = XCConfigurationList; 361 | buildConfigurations = ( 362 | 9798599E1CF45DE7009CB223 /* Debug */, 363 | 9798599F1CF45DE7009CB223 /* Release */, 364 | ); 365 | defaultConfigurationIsVisible = 0; 366 | defaultConfigurationName = Release; 367 | }; 368 | /* End XCConfigurationList section */ 369 | }; 370 | rootObject = 9798597E1CF45DE7009CB223 /* Project object */; 371 | } 372 | -------------------------------------------------------------------------------- /WCLRecordVideo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WCLRecordVideo.xcodeproj/xcuserdata/wangchonglei.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /WCLRecordVideo.xcodeproj/xcuserdata/wangchonglei.xcuserdatad/xcschemes/WCLRecordVideo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /WCLRecordVideo.xcodeproj/xcuserdata/wangchonglei.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | WCLRecordVideo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 979859851CF45DE7009CB223 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /WCLRecordVideo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // WCLRecordVideo 4 | // 5 | // Created by 王崇磊 on 16/5/24. 6 | // Copyright © 2016年 王崇磊. All rights reserved. 7 | // 8 | // 博客地址:http://blog.csdn.net/wang631106979/article/details/51498009 9 | 10 | #import 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (strong, nonatomic) UIWindow *window; 15 | 16 | 17 | @end 18 | 19 | -------------------------------------------------------------------------------- /WCLRecordVideo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // WCLRecordVideo 4 | // 5 | // Created by 王崇磊 on 16/5/24. 6 | // Copyright © 2016年 王崇磊. All rights reserved. 7 | // 8 | // 博客地址:http://blog.csdn.net/wang631106979/article/details/51498009 9 | 10 | #import "AppDelegate.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | // Override point for customization after application launch. 21 | return YES; 22 | } 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // 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. 26 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 27 | } 28 | 29 | - (void)applicationDidEnterBackground:(UIApplication *)application { 30 | // 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. 31 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 32 | } 33 | 34 | - (void)applicationWillEnterForeground:(UIApplication *)application { 35 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 36 | } 37 | 38 | - (void)applicationDidBecomeActive:(UIApplication *)application { 39 | // 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. 40 | } 41 | 42 | - (void)applicationWillTerminate:(UIApplication *)application { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/changeCamera.imageset/0-前后摄像头置换按钮@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/changeCamera.imageset/0-前后摄像头置换按钮@2x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/changeCamera.imageset/0-前后摄像头置换按钮@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/changeCamera.imageset/0-前后摄像头置换按钮@3x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/changeCamera.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "0-前后摄像头置换按钮@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "0-前后摄像头置换按钮@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/closeVideo#.imageset/0-关闭按钮-点击态@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/closeVideo#.imageset/0-关闭按钮-点击态@2x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/closeVideo#.imageset/0-关闭按钮-点击态@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/closeVideo#.imageset/0-关闭按钮-点击态@3x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/closeVideo#.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "0-关闭按钮-点击态@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "0-关闭按钮-点击态@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/closeVideo.imageset/0-关闭按钮-正常态@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/closeVideo.imageset/0-关闭按钮-正常态@2x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/closeVideo.imageset/0-关闭按钮-正常态@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/closeVideo.imageset/0-关闭按钮-正常态@3x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/closeVideo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "0-关闭按钮-正常态@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "0-关闭按钮-正常态@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/flashlightOff.imageset/0-闪光灯关@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/flashlightOff.imageset/0-闪光灯关@2x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/flashlightOff.imageset/0-闪光灯关@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/flashlightOff.imageset/0-闪光灯关@3x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/flashlightOff.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "0-闪光灯关@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "0-闪光灯关@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/flashlightOn.imageset/0-闪光灯开@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/flashlightOn.imageset/0-闪光灯开@2x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/flashlightOn.imageset/0-闪光灯开@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/flashlightOn.imageset/0-闪光灯开@3x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/flashlightOn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "0-闪光灯开@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "0-闪光灯开@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/locationVideo.imageset/0-本地视频@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/locationVideo.imageset/0-本地视频@2x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/locationVideo.imageset/0-本地视频@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/locationVideo.imageset/0-本地视频@3x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/locationVideo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "0-本地视频@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "0-本地视频@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/publishPlayVideo.imageset/3-播放按钮@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/publishPlayVideo.imageset/3-播放按钮@2x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/publishPlayVideo.imageset/3-播放按钮@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/publishPlayVideo.imageset/3-播放按钮@3x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/publishPlayVideo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "3-播放按钮@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "3-播放按钮@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/publishPlayVideoBig.imageset/5-播放按钮@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/publishPlayVideoBig.imageset/5-播放按钮@2x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/publishPlayVideoBig.imageset/5-播放按钮@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/publishPlayVideoBig.imageset/5-播放按钮@3x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/publishPlayVideoBig.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "5-播放按钮@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "5-播放按钮@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/videoLink.imageset/0-链接@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/videoLink.imageset/0-链接@2x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/videoLink.imageset/0-链接@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/videoLink.imageset/0-链接@3x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/videoLink.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "0-链接@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "0-链接@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/videoNext#.imageset/0-下一步按钮-点击态@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/videoNext#.imageset/0-下一步按钮-点击态@2x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/videoNext#.imageset/0-下一步按钮-点击态@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/videoNext#.imageset/0-下一步按钮-点击态@3x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/videoNext#.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "0-下一步按钮-点击态@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "0-下一步按钮-点击态@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/videoNext.imageset/0-下一步按钮-正常态@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/videoNext.imageset/0-下一步按钮-正常态@2x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/videoNext.imageset/0-下一步按钮-正常态@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/videoNext.imageset/0-下一步按钮-正常态@3x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/videoNext.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "0-下一步按钮-正常态@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "0-下一步按钮-正常态@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/videoPause.imageset/2-录制暂停按钮@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/videoPause.imageset/2-录制暂停按钮@2x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/videoPause.imageset/2-录制暂停按钮@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/videoPause.imageset/2-录制暂停按钮@3x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/videoPause.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "2-录制暂停按钮@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "2-录制暂停按钮@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/videoRecord.imageset/0-录制按钮@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/videoRecord.imageset/0-录制按钮@2x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/videoRecord.imageset/0-录制按钮@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/WCLRecordVideo/Assets.xcassets/videoRecord.imageset/0-录制按钮@3x.png -------------------------------------------------------------------------------- /WCLRecordVideo/Assets.xcassets/videoRecord.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "0-录制按钮@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "0-录制按钮@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /WCLRecordVideo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 88 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 125 | 133 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /WCLRecordVideo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleLocalizations 14 | 15 | zh 16 | en 17 | 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleSignature 25 | ???? 26 | CFBundleVersion 27 | 1 28 | LSRequiresIPhoneOS 29 | 30 | NSCameraUsageDescription 31 | 是否允许此App使用你的相机? 32 | NSMicrophoneUsageDescription 33 | 是否允许此App使用你的麦克风? 34 | NSPhotoLibraryUsageDescription 35 | 是否允许此App访问你的媒体资料库? 36 | UILaunchStoryboardName 37 | Launch Screen 38 | UIMainStoryboardFile 39 | Main 40 | UIRequiredDeviceCapabilities 41 | 42 | armv7 43 | 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | UISupportedInterfaceOrientations~ipad 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationPortraitUpsideDown 54 | UIInterfaceOrientationLandscapeLeft 55 | UIInterfaceOrientationLandscapeRight 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /WCLRecordVideo/Launch Screen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /WCLRecordVideo/WCLRecordProgressView/WCLRecordProgressView.h: -------------------------------------------------------------------------------- 1 | // 2 | // WCLRecordProgressView.h 3 | // Youqun 4 | // 5 | // Created by 王崇磊 on 16/5/19. 6 | // Copyright © 2016年 W_C__L. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | IB_DESIGNABLE 12 | 13 | @interface WCLRecordProgressView : UIView 14 | 15 | @property (assign, nonatomic) IBInspectable CGFloat progress;//当前进度 16 | @property (strong, nonatomic) IBInspectable UIColor *progressBgColor;//进度条背景颜色 17 | @property (strong, nonatomic) IBInspectable UIColor *progressColor;//进度条颜色 18 | @property (assign, nonatomic) CGFloat loadProgress;//加载好的进度 19 | @property (strong, nonatomic) UIColor *loadProgressColor;//已经加载好的进度颜色 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /WCLRecordVideo/WCLRecordProgressView/WCLRecordProgressView.m: -------------------------------------------------------------------------------- 1 | // 2 | // WCLRecordProgressView.m 3 | // Youqun 4 | // 5 | // Created by 王崇磊 on 16/5/19. 6 | // Copyright © 2016年 W_C__L. All rights reserved. 7 | // 8 | 9 | #import "WCLRecordProgressView.h" 10 | 11 | @implementation WCLRecordProgressView 12 | 13 | - (void)setProgress:(CGFloat)progress { 14 | _progress = progress; 15 | [self setNeedsDisplay]; 16 | } 17 | 18 | - (void)setProgressBgColor:(UIColor *)progressBgColor { 19 | _progressBgColor = progressBgColor; 20 | [self setNeedsDisplay]; 21 | } 22 | 23 | - (void)setloadProgressColor:(UIColor *)loadProgressColor { 24 | _loadProgressColor = loadProgressColor; 25 | [self setNeedsDisplay]; 26 | } 27 | 28 | - (void)setLoadProgress:(CGFloat)loadProgress { 29 | _loadProgress = loadProgress; 30 | [self setNeedsDisplay]; 31 | } 32 | 33 | - (void)setProgressColor:(UIColor *)progressColor { 34 | _progressColor = progressColor; 35 | [self setNeedsDisplay]; 36 | } 37 | 38 | - (void)drawRect:(CGRect)rect { 39 | CGContextRef context = UIGraphicsGetCurrentContext(); 40 | CGContextAddRect(context, CGRectMake(0, 0, rect.size.width, rect.size.height)); 41 | [self.progressBgColor set]; 42 | CGContextSetAlpha(context, 0.5); 43 | CGContextDrawPath(context, kCGPathFill); 44 | CGContextAddRect(context, CGRectMake(0, 0, rect.size.width*self.loadProgress, rect.size.height)); 45 | [self.progressBgColor set]; 46 | CGContextSetAlpha(context, 1); 47 | CGContextDrawPath(context, kCGPathFill); 48 | CGContextAddRect(context, CGRectMake(0, 0, rect.size.width*self.progress, rect.size.height)); 49 | [self.progressColor set]; 50 | CGContextSetAlpha(context, 1); 51 | CGContextDrawPath(context, kCGPathFill); 52 | } 53 | 54 | 55 | /* 56 | // Only override drawRect: if you perform custom drawing. 57 | // An empty implementation adversely affects performance during animation. 58 | - (void)drawRect:(CGRect)rect { 59 | // Drawing code 60 | } 61 | */ 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /WCLRecordVideo/WCLRecordVideo/WCLRecordEncoder/WCLRecordEncoder.h: -------------------------------------------------------------------------------- 1 | // 2 | // WCLRecordEncoder.h 3 | // WCL 4 | // 5 | // ************************************************** 6 | // * _____ * 7 | // * __ _ __ ___ \ / * 8 | // * \ \/ \/ / / __\ / / * 9 | // * \ _ / | (__ / / * 10 | // * \/ \/ \___/ / /__ * 11 | // * /_____/ * 12 | // * * 13 | // ************************************************** 14 | // Github :https://github.com/631106979 15 | // HomePage:https://imwcl.com 16 | // CSDN :http://blog.csdn.net/wang631106979 17 | // 18 | // Created by 王崇磊 on 16/9/14. 19 | // Copyright © 2016年 王崇磊. All rights reserved. 20 | // 21 | // @class WCLRecordEncoder 22 | // @abstract 视频编码类 23 | // @discussion 视频编码类 24 | // 25 | 26 | #import 27 | #import 28 | 29 | /** 30 | * 写入并编码视频的的类 31 | */ 32 | @interface WCLRecordEncoder : NSObject 33 | 34 | @property (nonatomic, readonly) NSString *path; 35 | 36 | /** 37 | * WCLRecordEncoder遍历构造器的 38 | * 39 | * @param path 媒体存发路径 40 | * @param cy 视频分辨率的高 41 | * @param cx 视频分辨率的宽 42 | * @param ch 音频通道 43 | * @param rate 音频的采样比率 44 | * 45 | * @return WCLRecordEncoder的实体 46 | */ 47 | + (WCLRecordEncoder*)encoderForPath:(NSString*)path Height:(NSInteger)cy width:(NSInteger)cx channels: (int)ch samples:(Float64)rate; 48 | 49 | /** 50 | * 初始化方法 51 | * 52 | * @param path 媒体存发路径 53 | * @param cy 视频分辨率的高 54 | * @param cx 视频分辨率的宽 55 | * @param ch 音频通道 56 | * @param rate 音频的采样率 57 | * 58 | * @return WCLRecordEncoder的实体 59 | */ 60 | - (instancetype)initPath:(NSString*)path Height:(NSInteger)cy width:(NSInteger)cx channels: (int)ch samples:(Float64)rate; 61 | 62 | /** 63 | * 完成视频录制时调用 64 | * 65 | * @param handler 完成的回掉block 66 | */ 67 | - (void)finishWithCompletionHandler:(void (^)(void))handler; 68 | 69 | /** 70 | * 通过这个方法写入数据 71 | * 72 | * @param sampleBuffer 写入的数据 73 | * @param isVideo 是否写入的是视频 74 | * 75 | * @return 写入是否成功 76 | */ 77 | - (BOOL)encodeFrame:(CMSampleBufferRef)sampleBuffer isVideo:(BOOL)isVideo; 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /WCLRecordVideo/WCLRecordVideo/WCLRecordEncoder/WCLRecordEncoder.m: -------------------------------------------------------------------------------- 1 | // 2 | // WCLRecordEncoder.m 3 | // WCL 4 | // 5 | // ************************************************** 6 | // * _____ * 7 | // * __ _ __ ___ \ / * 8 | // * \ \/ \/ / / __\ / / * 9 | // * \ _ / | (__ / / * 10 | // * \/ \/ \___/ / /__ * 11 | // * /_____/ * 12 | // * * 13 | // ************************************************** 14 | // Github :https://github.com/631106979 15 | // HomePage:https://imwcl.com 16 | // CSDN :http://blog.csdn.net/wang631106979 17 | // 18 | // Created by 王崇磊 on 16/9/14. 19 | // Copyright © 2016年 王崇磊. All rights reserved. 20 | // 21 | // @class WCLRecordEncoder 22 | // @abstract 视频编码类 23 | // @discussion 应用的相关扩展 24 | // 25 | // 博客地址:http://blog.csdn.net/wang631106979/article/details/51498009 26 | 27 | #import "WCLRecordEncoder.h" 28 | 29 | @interface WCLRecordEncoder () 30 | 31 | @property (nonatomic, strong) AVAssetWriter *writer;//媒体写入对象 32 | @property (nonatomic, strong) AVAssetWriterInput *videoInput;//视频写入 33 | @property (nonatomic, strong) AVAssetWriterInput *audioInput;//音频写入 34 | @property (nonatomic, strong) NSString *path;//写入路径 35 | 36 | @end 37 | 38 | @implementation WCLRecordEncoder 39 | 40 | - (void)dealloc { 41 | _writer = nil; 42 | _videoInput = nil; 43 | _audioInput = nil; 44 | _path = nil; 45 | } 46 | 47 | //WCLRecordEncoder遍历构造器的 48 | + (WCLRecordEncoder*)encoderForPath:(NSString*) path Height:(NSInteger) cy width:(NSInteger) cx channels: (int) ch samples:(Float64) rate { 49 | WCLRecordEncoder* enc = [WCLRecordEncoder alloc]; 50 | return [enc initPath:path Height:cy width:cx channels:ch samples:rate]; 51 | } 52 | 53 | //初始化方法 54 | - (instancetype)initPath:(NSString*)path Height:(NSInteger)cy width:(NSInteger)cx channels:(int)ch samples:(Float64) rate { 55 | self = [super init]; 56 | if (self) { 57 | self.path = path; 58 | //先把路径下的文件给删除掉,保证录制的文件是最新的 59 | [[NSFileManager defaultManager] removeItemAtPath:self.path error:nil]; 60 | NSURL* url = [NSURL fileURLWithPath:self.path]; 61 | //初始化写入媒体类型为MP4类型 62 | _writer = [AVAssetWriter assetWriterWithURL:url fileType:AVFileTypeMPEG4 error:nil]; 63 | //使其更适合在网络上播放 64 | _writer.shouldOptimizeForNetworkUse = YES; 65 | //初始化视频输出 66 | [self initVideoInputHeight:cy width:cx]; 67 | //确保采集到rate和ch 68 | if (rate != 0 && ch != 0) { 69 | //初始化音频输出 70 | [self initAudioInputChannels:ch samples:rate]; 71 | } 72 | } 73 | return self; 74 | } 75 | 76 | //初始化视频输入 77 | - (void)initVideoInputHeight:(NSInteger)cy width:(NSInteger)cx { 78 | //录制视频的一些配置,分辨率,编码方式等等 79 | NSDictionary* settings = [NSDictionary dictionaryWithObjectsAndKeys: 80 | AVVideoCodecH264, AVVideoCodecKey, 81 | [NSNumber numberWithInteger: cx], AVVideoWidthKey, 82 | [NSNumber numberWithInteger: cy], AVVideoHeightKey, 83 | nil]; 84 | //初始化视频写入类 85 | _videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:settings]; 86 | //表明输入是否应该调整其处理为实时数据源的数据 87 | _videoInput.expectsMediaDataInRealTime = YES; 88 | //将视频输入源加入 89 | [_writer addInput:_videoInput]; 90 | } 91 | 92 | //初始化音频输入 93 | - (void)initAudioInputChannels:(int)ch samples:(Float64)rate { 94 | //音频的一些配置包括音频各种这里为AAC,音频通道、采样率和音频的比特率 95 | NSDictionary *settings = [NSDictionary dictionaryWithObjectsAndKeys: 96 | [ NSNumber numberWithInt: kAudioFormatMPEG4AAC], AVFormatIDKey, 97 | [ NSNumber numberWithInt: ch], AVNumberOfChannelsKey, 98 | [ NSNumber numberWithFloat: rate], AVSampleRateKey, 99 | [ NSNumber numberWithInt: 128000], AVEncoderBitRateKey, 100 | nil]; 101 | //初始化音频写入类 102 | _audioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:settings]; 103 | //表明输入是否应该调整其处理为实时数据源的数据 104 | _audioInput.expectsMediaDataInRealTime = YES; 105 | //将音频输入源加入 106 | [_writer addInput:_audioInput]; 107 | 108 | } 109 | 110 | //完成视频录制时调用 111 | - (void)finishWithCompletionHandler:(void (^)(void))handler { 112 | [_writer finishWritingWithCompletionHandler: handler]; 113 | } 114 | 115 | //通过这个方法写入数据 116 | - (BOOL)encodeFrame:(CMSampleBufferRef) sampleBuffer isVideo:(BOOL)isVideo { 117 | //数据是否准备写入 118 | if (CMSampleBufferDataIsReady(sampleBuffer)) { 119 | //写入状态为未知,保证视频先写入 120 | if (_writer.status == AVAssetWriterStatusUnknown && isVideo) { 121 | //获取开始写入的CMTime 122 | CMTime startTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); 123 | //开始写入 124 | [_writer startWriting]; 125 | [_writer startSessionAtSourceTime:startTime]; 126 | } 127 | //写入失败 128 | if (_writer.status == AVAssetWriterStatusFailed) { 129 | NSLog(@"writer error %@", _writer.error.localizedDescription); 130 | return NO; 131 | } 132 | //判断是否是视频 133 | if (isVideo) { 134 | //视频输入是否准备接受更多的媒体数据 135 | if (_videoInput.readyForMoreMediaData == YES) { 136 | //拼接数据 137 | [_videoInput appendSampleBuffer:sampleBuffer]; 138 | return YES; 139 | } 140 | }else { 141 | //音频输入是否准备接受更多的媒体数据 142 | if (_audioInput.readyForMoreMediaData) { 143 | //拼接数据 144 | [_audioInput appendSampleBuffer:sampleBuffer]; 145 | return YES; 146 | } 147 | } 148 | } 149 | return NO; 150 | } 151 | 152 | @end 153 | -------------------------------------------------------------------------------- /WCLRecordVideo/WCLRecordVideo/WCLRecordEngine/WCLRecordEngine.h: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // WCLRecordEngine.h 4 | // WCL 5 | // 6 | // ************************************************** 7 | // * _____ * 8 | // * __ _ __ ___ \ / * 9 | // * \ \/ \/ / / __\ / / * 10 | // * \ _ / | (__ / / * 11 | // * \/ \/ \___/ / /__ * 12 | // * /_____/ * 13 | // * * 14 | // ************************************************** 15 | // Github :https://github.com/631106979 16 | // HomePage:https://imwcl.com 17 | // CSDN :http://blog.csdn.net/wang631106979 18 | // 19 | // Created by 王崇磊 on 16/9/14. 20 | // Copyright © 2016年 王崇磊. All rights reserved. 21 | // 22 | // @class WCLRecordEngine 23 | // @abstract 视频录制类 24 | // @discussion 视频录制类 25 | // 26 | 27 | #import 28 | #import 29 | #import 30 | 31 | @protocol WCLRecordEngineDelegate 32 | 33 | - (void)recordProgress:(CGFloat)progress; 34 | 35 | @end 36 | 37 | @interface WCLRecordEngine : NSObject 38 | 39 | @property (atomic, assign, readonly) BOOL isCapturing;//正在录制 40 | @property (atomic, assign, readonly) BOOL isPaused;//是否暂停 41 | @property (atomic, assign, readonly) CGFloat currentRecordTime;//当前录制时间 42 | @property (atomic, assign) CGFloat maxRecordTime;//录制最长时间 43 | @property (weak, nonatomic) iddelegate; 44 | @property (atomic, strong) NSString *videoPath;//视频路径 45 | 46 | //捕获到的视频呈现的layer 47 | - (AVCaptureVideoPreviewLayer *)previewLayer; 48 | //启动录制功能 49 | - (void)startUp; 50 | //关闭录制功能 51 | - (void)shutdown; 52 | //开始录制 53 | - (void) startCapture; 54 | //暂停录制 55 | - (void) pauseCapture; 56 | //停止录制 57 | - (void) stopCaptureHandler:(void (^)(UIImage *movieImage))handler; 58 | //继续录制 59 | - (void) resumeCapture; 60 | //开启闪光灯 61 | - (void)openFlashLight; 62 | //关闭闪光灯 63 | - (void)closeFlashLight; 64 | //切换前后置摄像头 65 | - (void)changeCameraInputDeviceisFront:(BOOL)isFront; 66 | //将mov的视频转成mp4 67 | - (void)changeMovToMp4:(NSURL *)mediaURL dataBlock:(void (^)(UIImage *movieImage))handler; 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /WCLRecordVideo/WCLRecordVideo/WCLRecordEngine/WCLRecordEngine.m: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // WCLRecordEngine.m 4 | // WCL 5 | // 6 | // ************************************************** 7 | // * _____ * 8 | // * __ _ __ ___ \ / * 9 | // * \ \/ \/ / / __\ / / * 10 | // * \ _ / | (__ / / * 11 | // * \/ \/ \___/ / /__ * 12 | // * /_____/ * 13 | // * * 14 | // ************************************************** 15 | // Github :https://github.com/631106979 16 | // HomePage:https://imwcl.com 17 | // CSDN :http://blog.csdn.net/wang631106979 18 | // 19 | // Created by 王崇磊 on 16/9/14. 20 | // Copyright © 2016年 王崇磊. All rights reserved. 21 | // 22 | // @class WCLRecordEngine 23 | // @abstract 视频录制类 24 | // @discussion 视频录制类 25 | // 26 | // 博客地址:http://blog.csdn.net/wang631106979/article/details/51498009 27 | 28 | #import "WCLRecordEngine.h" 29 | #import "WCLRecordEncoder.h" 30 | #import 31 | #import 32 | 33 | @interface WCLRecordEngine () { 34 | CMTime _timeOffset;//录制的偏移CMTime 35 | CMTime _lastVideo;//记录上一次视频数据文件的CMTime 36 | CMTime _lastAudio;//记录上一次音频数据文件的CMTime 37 | 38 | NSInteger _cx;//视频分辨的宽 39 | NSInteger _cy;//视频分辨的高 40 | int _channels;//音频通道 41 | Float64 _samplerate;//音频采样率 42 | } 43 | 44 | @property (strong, nonatomic) WCLRecordEncoder *recordEncoder;//录制编码 45 | @property (strong, nonatomic) AVCaptureSession *recordSession;//捕获视频的会话 46 | @property (strong, nonatomic) AVCaptureVideoPreviewLayer *previewLayer;//捕获到的视频呈现的layer 47 | @property (strong, nonatomic) AVCaptureDeviceInput *backCameraInput;//后置摄像头输入 48 | @property (strong, nonatomic) AVCaptureDeviceInput *frontCameraInput;//前置摄像头输入 49 | @property (strong, nonatomic) AVCaptureDeviceInput *audioMicInput;//麦克风输入 50 | @property (copy , nonatomic) dispatch_queue_t captureQueue;//录制的队列 51 | @property (strong, nonatomic) AVCaptureConnection *audioConnection;//音频录制连接 52 | @property (strong, nonatomic) AVCaptureConnection *videoConnection;//视频录制连接 53 | @property (strong, nonatomic) AVCaptureVideoDataOutput *videoOutput;//视频输出 54 | @property (strong, nonatomic) AVCaptureAudioDataOutput *audioOutput;//音频输出 55 | @property (atomic, assign) BOOL isCapturing;//正在录制 56 | @property (atomic, assign) BOOL isPaused;//是否暂停 57 | @property (atomic, assign) BOOL discont;//是否中断 58 | @property (atomic, assign) CMTime startTime;//开始录制的时间 59 | @property (atomic, assign) CGFloat currentRecordTime;//当前录制时间 60 | 61 | @end 62 | 63 | @implementation WCLRecordEngine 64 | 65 | - (void)dealloc { 66 | [_recordSession stopRunning]; 67 | _captureQueue = nil; 68 | _recordSession = nil; 69 | _previewLayer = nil; 70 | _backCameraInput = nil; 71 | _frontCameraInput = nil; 72 | _audioOutput = nil; 73 | _videoOutput = nil; 74 | _audioConnection = nil; 75 | _videoConnection = nil; 76 | _recordEncoder = nil; 77 | } 78 | 79 | - (instancetype)init 80 | { 81 | self = [super init]; 82 | if (self) { 83 | self.maxRecordTime = 60.0f; 84 | } 85 | return self; 86 | } 87 | 88 | #pragma mark - 公开的方法 89 | //启动录制功能 90 | - (void)startUp { 91 | // NSLog(@"启动录制功能"); 92 | self.startTime = CMTimeMake(0, 0); 93 | self.isCapturing = NO; 94 | self.isPaused = NO; 95 | self.discont = NO; 96 | [self.recordSession startRunning]; 97 | } 98 | //关闭录制功能 99 | - (void)shutdown { 100 | _startTime = CMTimeMake(0, 0); 101 | if (_recordSession) { 102 | [_recordSession stopRunning]; 103 | } 104 | [_recordEncoder finishWithCompletionHandler:^{ 105 | // NSLog(@"录制完成"); 106 | }]; 107 | } 108 | 109 | //开始录制 110 | - (void) startCapture { 111 | @synchronized(self) { 112 | if (!self.isCapturing) { 113 | // NSLog(@"开始录制"); 114 | self.recordEncoder = nil; 115 | self.isPaused = NO; 116 | self.discont = NO; 117 | _timeOffset = CMTimeMake(0, 0); 118 | self.isCapturing = YES; 119 | } 120 | } 121 | } 122 | //暂停录制 123 | - (void) pauseCapture { 124 | @synchronized(self) { 125 | if (self.isCapturing) { 126 | // NSLog(@"暂停录制"); 127 | self.isPaused = YES; 128 | self.discont = YES; 129 | } 130 | } 131 | } 132 | //继续录制 133 | - (void) resumeCapture { 134 | @synchronized(self) { 135 | if (self.isPaused) { 136 | // NSLog(@"继续录制"); 137 | self.isPaused = NO; 138 | } 139 | } 140 | } 141 | //停止录制 142 | - (void) stopCaptureHandler:(void (^)(UIImage *movieImage))handler { 143 | @synchronized(self) { 144 | if (self.isCapturing) { 145 | NSString* path = self.recordEncoder.path; 146 | NSURL* url = [NSURL fileURLWithPath:path]; 147 | self.isCapturing = NO; 148 | dispatch_async(_captureQueue, ^{ 149 | [self.recordEncoder finishWithCompletionHandler:^{ 150 | self.isCapturing = NO; 151 | self.recordEncoder = nil; 152 | self.startTime = CMTimeMake(0, 0); 153 | self.currentRecordTime = 0; 154 | if ([self.delegate respondsToSelector:@selector(recordProgress:)]) { 155 | dispatch_async(dispatch_get_main_queue(), ^{ 156 | [self.delegate recordProgress:self.currentRecordTime/self.maxRecordTime]; 157 | }); 158 | } 159 | [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ 160 | [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:url]; 161 | } completionHandler:^(BOOL success, NSError * _Nullable error) { 162 | NSLog(@"保存成功"); 163 | }]; 164 | [self movieToImageHandler:handler]; 165 | }]; 166 | }); 167 | } 168 | } 169 | } 170 | 171 | //获取视频第一帧的图片 172 | - (void)movieToImageHandler:(void (^)(UIImage *movieImage))handler { 173 | NSURL *url = [NSURL fileURLWithPath:self.videoPath]; 174 | AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil]; 175 | AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:asset]; 176 | generator.appliesPreferredTrackTransform = TRUE; 177 | CMTime thumbTime = CMTimeMakeWithSeconds(0, 60); 178 | generator.apertureMode = AVAssetImageGeneratorApertureModeEncodedPixels; 179 | AVAssetImageGeneratorCompletionHandler generatorHandler = 180 | ^(CMTime requestedTime, CGImageRef im, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error){ 181 | if (result == AVAssetImageGeneratorSucceeded) { 182 | UIImage *thumbImg = [UIImage imageWithCGImage:im]; 183 | if (handler) { 184 | dispatch_async(dispatch_get_main_queue(), ^{ 185 | handler(thumbImg); 186 | }); 187 | } 188 | } 189 | }; 190 | [generator generateCGImagesAsynchronouslyForTimes: 191 | [NSArray arrayWithObject:[NSValue valueWithCMTime:thumbTime]] completionHandler:generatorHandler]; 192 | } 193 | 194 | #pragma mark - set、get方法 195 | //捕获视频的会话 196 | - (AVCaptureSession *)recordSession { 197 | if (_recordSession == nil) { 198 | _recordSession = [[AVCaptureSession alloc] init]; 199 | //添加后置摄像头的输出 200 | if ([_recordSession canAddInput:self.backCameraInput]) { 201 | [_recordSession addInput:self.backCameraInput]; 202 | } 203 | //添加后置麦克风的输出 204 | if ([_recordSession canAddInput:self.audioMicInput]) { 205 | [_recordSession addInput:self.audioMicInput]; 206 | } 207 | //添加视频输出 208 | if ([_recordSession canAddOutput:self.videoOutput]) { 209 | [_recordSession addOutput:self.videoOutput]; 210 | //设置视频的分辨率 211 | _cx = 720; 212 | _cy = 1280; 213 | } 214 | //添加音频输出 215 | if ([_recordSession canAddOutput:self.audioOutput]) { 216 | [_recordSession addOutput:self.audioOutput]; 217 | } 218 | //设置视频录制的方向 219 | self.videoConnection.videoOrientation = AVCaptureVideoOrientationPortrait; 220 | } 221 | return _recordSession; 222 | } 223 | 224 | //后置摄像头输入 225 | - (AVCaptureDeviceInput *)backCameraInput { 226 | if (_backCameraInput == nil) { 227 | NSError *error; 228 | _backCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self backCamera] error:&error]; 229 | if (error) { 230 | NSLog(@"获取后置摄像头失败~"); 231 | } 232 | } 233 | return _backCameraInput; 234 | } 235 | 236 | //前置摄像头输入 237 | - (AVCaptureDeviceInput *)frontCameraInput { 238 | if (_frontCameraInput == nil) { 239 | NSError *error; 240 | _frontCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self frontCamera] error:&error]; 241 | if (error) { 242 | NSLog(@"获取前置摄像头失败~"); 243 | } 244 | } 245 | return _frontCameraInput; 246 | } 247 | 248 | //麦克风输入 249 | - (AVCaptureDeviceInput *)audioMicInput { 250 | if (_audioMicInput == nil) { 251 | AVCaptureDevice *mic = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; 252 | NSError *error; 253 | _audioMicInput = [AVCaptureDeviceInput deviceInputWithDevice:mic error:&error]; 254 | if (error) { 255 | NSLog(@"获取麦克风失败~"); 256 | } 257 | } 258 | return _audioMicInput; 259 | } 260 | 261 | //视频输出 262 | - (AVCaptureVideoDataOutput *)videoOutput { 263 | if (_videoOutput == nil) { 264 | _videoOutput = [[AVCaptureVideoDataOutput alloc] init]; 265 | [_videoOutput setSampleBufferDelegate:self queue:self.captureQueue]; 266 | NSDictionary* setcapSettings = [NSDictionary dictionaryWithObjectsAndKeys: 267 | [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange], kCVPixelBufferPixelFormatTypeKey, 268 | nil]; 269 | _videoOutput.videoSettings = setcapSettings; 270 | } 271 | return _videoOutput; 272 | } 273 | 274 | //音频输出 275 | - (AVCaptureAudioDataOutput *)audioOutput { 276 | if (_audioOutput == nil) { 277 | _audioOutput = [[AVCaptureAudioDataOutput alloc] init]; 278 | [_audioOutput setSampleBufferDelegate:self queue:self.captureQueue]; 279 | } 280 | return _audioOutput; 281 | } 282 | 283 | //视频连接 284 | - (AVCaptureConnection *)videoConnection { 285 | _videoConnection = [self.videoOutput connectionWithMediaType:AVMediaTypeVideo]; 286 | return _videoConnection; 287 | } 288 | 289 | //音频连接 290 | - (AVCaptureConnection *)audioConnection { 291 | if (_audioConnection == nil) { 292 | _audioConnection = [self.audioOutput connectionWithMediaType:AVMediaTypeAudio]; 293 | } 294 | return _audioConnection; 295 | } 296 | 297 | //捕获到的视频呈现的layer 298 | - (AVCaptureVideoPreviewLayer *)previewLayer { 299 | if (_previewLayer == nil) { 300 | //通过AVCaptureSession初始化 301 | AVCaptureVideoPreviewLayer *preview = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.recordSession]; 302 | //设置比例为铺满全屏 303 | preview.videoGravity = AVLayerVideoGravityResizeAspectFill; 304 | _previewLayer = preview; 305 | } 306 | return _previewLayer; 307 | } 308 | 309 | //录制的队列 310 | - (dispatch_queue_t)captureQueue { 311 | if (_captureQueue == nil) { 312 | _captureQueue = dispatch_queue_create("cn.qiuyouqun.im.wclrecordengine.capture", DISPATCH_QUEUE_SERIAL); 313 | } 314 | return _captureQueue; 315 | } 316 | 317 | #pragma mark - 切换动画 318 | - (void)changeCameraAnimation { 319 | CATransition *changeAnimation = [CATransition animation]; 320 | changeAnimation.delegate = self; 321 | changeAnimation.duration = 0.45; 322 | changeAnimation.type = @"oglFlip"; 323 | changeAnimation.subtype = kCATransitionFromRight; 324 | changeAnimation.timingFunction = UIViewAnimationCurveEaseInOut; 325 | [self.previewLayer addAnimation:changeAnimation forKey:@"changeAnimation"]; 326 | } 327 | 328 | - (void)animationDidStart:(CAAnimation *)anim { 329 | self.videoConnection.videoOrientation = AVCaptureVideoOrientationPortrait; 330 | [self.recordSession startRunning]; 331 | } 332 | 333 | #pragma -mark 将mov文件转为MP4文件 334 | - (void)changeMovToMp4:(NSURL *)mediaURL dataBlock:(void (^)(UIImage *movieImage))handler { 335 | AVAsset *video = [AVAsset assetWithURL:mediaURL]; 336 | AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:video presetName:AVAssetExportPreset1280x720]; 337 | exportSession.shouldOptimizeForNetworkUse = YES; 338 | exportSession.outputFileType = AVFileTypeMPEG4; 339 | NSString * basePath=[self getVideoCachePath]; 340 | 341 | self.videoPath = [basePath stringByAppendingPathComponent:[self getUploadFile_type:@"video" fileType:@"mp4"]]; 342 | exportSession.outputURL = [NSURL fileURLWithPath:self.videoPath]; 343 | [exportSession exportAsynchronouslyWithCompletionHandler:^{ 344 | [self movieToImageHandler:handler]; 345 | }]; 346 | } 347 | 348 | #pragma mark - 视频相关 349 | //返回前置摄像头 350 | - (AVCaptureDevice *)frontCamera { 351 | return [self cameraWithPosition:AVCaptureDevicePositionFront]; 352 | } 353 | 354 | //返回后置摄像头 355 | - (AVCaptureDevice *)backCamera { 356 | return [self cameraWithPosition:AVCaptureDevicePositionBack]; 357 | } 358 | 359 | 360 | //切换前后置摄像头 361 | - (void)changeCameraInputDeviceisFront:(BOOL)isFront { 362 | if (isFront) { 363 | [self.recordSession stopRunning]; 364 | [self.recordSession removeInput:self.backCameraInput]; 365 | if ([self.recordSession canAddInput:self.frontCameraInput]) { 366 | [self changeCameraAnimation]; 367 | [self.recordSession addInput:self.frontCameraInput]; 368 | } 369 | }else { 370 | [self.recordSession stopRunning]; 371 | [self.recordSession removeInput:self.frontCameraInput]; 372 | if ([self.recordSession canAddInput:self.backCameraInput]) { 373 | [self changeCameraAnimation]; 374 | [self.recordSession addInput:self.backCameraInput]; 375 | } 376 | } 377 | } 378 | 379 | //用来返回是前置摄像头还是后置摄像头 380 | - (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition) position { 381 | //返回和视频录制相关的所有默认设备 382 | NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; 383 | //遍历这些设备返回跟position相关的设备 384 | for (AVCaptureDevice *device in devices) { 385 | if ([device position] == position) { 386 | return device; 387 | } 388 | } 389 | return nil; 390 | } 391 | 392 | //开启闪光灯 393 | - (void)openFlashLight { 394 | AVCaptureDevice *backCamera = [self backCamera]; 395 | if (backCamera.torchMode == AVCaptureTorchModeOff) { 396 | [backCamera lockForConfiguration:nil]; 397 | backCamera.torchMode = AVCaptureTorchModeOn; 398 | backCamera.flashMode = AVCaptureFlashModeOn; 399 | [backCamera unlockForConfiguration]; 400 | } 401 | } 402 | //关闭闪光灯 403 | - (void)closeFlashLight { 404 | AVCaptureDevice *backCamera = [self backCamera]; 405 | if (backCamera.torchMode == AVCaptureTorchModeOn) { 406 | [backCamera lockForConfiguration:nil]; 407 | backCamera.torchMode = AVCaptureTorchModeOff; 408 | backCamera.flashMode = AVCaptureTorchModeOff; 409 | [backCamera unlockForConfiguration]; 410 | } 411 | } 412 | 413 | //获得视频存放地址 414 | - (NSString *)getVideoCachePath { 415 | NSString *videoCache = [NSTemporaryDirectory() stringByAppendingPathComponent:@"videos"] ; 416 | BOOL isDir = NO; 417 | NSFileManager *fileManager = [NSFileManager defaultManager]; 418 | BOOL existed = [fileManager fileExistsAtPath:videoCache isDirectory:&isDir]; 419 | if ( !(isDir == YES && existed == YES) ) { 420 | [fileManager createDirectoryAtPath:videoCache withIntermediateDirectories:YES attributes:nil error:nil]; 421 | }; 422 | return videoCache; 423 | } 424 | 425 | - (NSString *)getUploadFile_type:(NSString *)type fileType:(NSString *)fileType { 426 | NSTimeInterval now = [[NSDate date] timeIntervalSince1970]; 427 | NSDateFormatter * formatter = [[NSDateFormatter alloc] init]; 428 | [formatter setDateFormat:@"HHmmss"]; 429 | NSDate * NowDate = [NSDate dateWithTimeIntervalSince1970:now]; 430 | ; 431 | NSString * timeStr = [formatter stringFromDate:NowDate]; 432 | NSString *fileName = [NSString stringWithFormat:@"%@_%@.%@",type,timeStr,fileType]; 433 | return fileName; 434 | } 435 | 436 | #pragma mark - 写入数据 437 | - (void) captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { 438 | BOOL isVideo = YES; 439 | @synchronized(self) { 440 | if (!self.isCapturing || self.isPaused) { 441 | return; 442 | } 443 | if (captureOutput != self.videoOutput) { 444 | isVideo = NO; 445 | } 446 | //初始化编码器,当有音频和视频参数时创建编码器 447 | if ((self.recordEncoder == nil) && !isVideo) { 448 | CMFormatDescriptionRef fmt = CMSampleBufferGetFormatDescription(sampleBuffer); 449 | [self setAudioFormat:fmt]; 450 | NSString *videoName = [self getUploadFile_type:@"video" fileType:@"mp4"]; 451 | self.videoPath = [[self getVideoCachePath] stringByAppendingPathComponent:videoName]; 452 | self.recordEncoder = [WCLRecordEncoder encoderForPath:self.videoPath Height:_cy width:_cx channels:_channels samples:_samplerate]; 453 | } 454 | //判断是否中断录制过 455 | if (self.discont) { 456 | if (isVideo) { 457 | return; 458 | } 459 | self.discont = NO; 460 | // 计算暂停的时间 461 | CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); 462 | CMTime last = isVideo ? _lastVideo : _lastAudio; 463 | if (last.flags & kCMTimeFlags_Valid) { 464 | if (_timeOffset.flags & kCMTimeFlags_Valid) { 465 | pts = CMTimeSubtract(pts, _timeOffset); 466 | } 467 | CMTime offset = CMTimeSubtract(pts, last); 468 | if (_timeOffset.value == 0) { 469 | _timeOffset = offset; 470 | }else { 471 | _timeOffset = CMTimeAdd(_timeOffset, offset); 472 | } 473 | } 474 | _lastVideo.flags = 0; 475 | _lastAudio.flags = 0; 476 | } 477 | // 增加sampleBuffer的引用计时,这样我们可以释放这个或修改这个数据,防止在修改时被释放 478 | CFRetain(sampleBuffer); 479 | if (_timeOffset.value > 0) { 480 | CFRelease(sampleBuffer); 481 | //根据得到的timeOffset调整 482 | sampleBuffer = [self adjustTime:sampleBuffer by:_timeOffset]; 483 | } 484 | // 记录暂停上一次录制的时间 485 | CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); 486 | CMTime dur = CMSampleBufferGetDuration(sampleBuffer); 487 | if (dur.value > 0) { 488 | pts = CMTimeAdd(pts, dur); 489 | } 490 | if (isVideo) { 491 | _lastVideo = pts; 492 | }else { 493 | _lastAudio = pts; 494 | } 495 | } 496 | CMTime dur = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); 497 | if (self.startTime.value == 0) { 498 | self.startTime = dur; 499 | } 500 | CMTime sub = CMTimeSubtract(dur, self.startTime); 501 | self.currentRecordTime = CMTimeGetSeconds(sub); 502 | if (self.currentRecordTime > self.maxRecordTime) { 503 | if (self.currentRecordTime - self.maxRecordTime < 0.1) { 504 | if ([self.delegate respondsToSelector:@selector(recordProgress:)]) { 505 | dispatch_async(dispatch_get_main_queue(), ^{ 506 | [self.delegate recordProgress:self.currentRecordTime/self.maxRecordTime]; 507 | }); 508 | } 509 | } 510 | return; 511 | } 512 | if ([self.delegate respondsToSelector:@selector(recordProgress:)]) { 513 | dispatch_async(dispatch_get_main_queue(), ^{ 514 | [self.delegate recordProgress:self.currentRecordTime/self.maxRecordTime]; 515 | }); 516 | } 517 | // 进行数据编码 518 | [self.recordEncoder encodeFrame:sampleBuffer isVideo:isVideo]; 519 | CFRelease(sampleBuffer); 520 | } 521 | 522 | //设置音频格式 523 | - (void)setAudioFormat:(CMFormatDescriptionRef)fmt { 524 | const AudioStreamBasicDescription *asbd = CMAudioFormatDescriptionGetStreamBasicDescription(fmt); 525 | _samplerate = asbd->mSampleRate; 526 | _channels = asbd->mChannelsPerFrame; 527 | 528 | } 529 | 530 | //调整媒体数据的时间 531 | - (CMSampleBufferRef)adjustTime:(CMSampleBufferRef)sample by:(CMTime)offset { 532 | CMItemCount count; 533 | CMSampleBufferGetSampleTimingInfoArray(sample, 0, nil, &count); 534 | CMSampleTimingInfo* pInfo = malloc(sizeof(CMSampleTimingInfo) * count); 535 | CMSampleBufferGetSampleTimingInfoArray(sample, count, pInfo, &count); 536 | for (CMItemCount i = 0; i < count; i++) { 537 | pInfo[i].decodeTimeStamp = CMTimeSubtract(pInfo[i].decodeTimeStamp, offset); 538 | pInfo[i].presentationTimeStamp = CMTimeSubtract(pInfo[i].presentationTimeStamp, offset); 539 | } 540 | CMSampleBufferRef sout; 541 | CMSampleBufferCreateCopyWithNewTiming(nil, sample, count, pInfo, &sout); 542 | free(pInfo); 543 | return sout; 544 | } 545 | 546 | @end 547 | -------------------------------------------------------------------------------- /WCLRecordVideo/WCLRecordVideoVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // WCLRecordVideoVC.h 3 | // Youqun 4 | // 5 | // Created by 王崇磊 on 16/5/16. 6 | // Copyright © 2016年 W_C__L. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface WCLRecordVideoVC :UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /WCLRecordVideo/WCLRecordVideoVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // YQRecordVideoVC.m 3 | // Youqun 4 | // 5 | // Created by 王崇磊 on 16/5/16. 6 | // Copyright © 2016年 W_C__L. All rights reserved. 7 | // 8 | // 博客地址:http://blog.csdn.net/wang631106979/article/details/51498009 9 | 10 | #import "WCLRecordVideoVC.h" 11 | #import "WCLRecordEngine.h" 12 | #import "WCLRecordProgressView.h" 13 | #import 14 | #import 15 | 16 | typedef NS_ENUM(NSUInteger, UploadVieoStyle) { 17 | VideoRecord = 0, 18 | VideoLocation, 19 | }; 20 | 21 | @interface WCLRecordVideoVC () 22 | 23 | @property (weak, nonatomic) IBOutlet UIButton *flashLightBT; 24 | @property (weak, nonatomic) IBOutlet UIButton *changeCameraBT; 25 | @property (weak, nonatomic) IBOutlet UIButton *recordNextBT; 26 | @property (weak, nonatomic) IBOutlet UIButton *recordBt; 27 | @property (weak, nonatomic) IBOutlet UIButton *locationVideoBT; 28 | @property (weak, nonatomic) IBOutlet NSLayoutConstraint *topViewTop; 29 | @property (weak, nonatomic) IBOutlet WCLRecordProgressView *progressView; 30 | @property (strong, nonatomic) WCLRecordEngine *recordEngine; 31 | @property (assign, nonatomic) BOOL allowRecord;//允许录制 32 | @property (assign, nonatomic) UploadVieoStyle videoStyle;//视频的类型 33 | @property (strong, nonatomic) UIImagePickerController *moviePicker;//视频选择器 34 | @property (strong, nonatomic) MPMoviePlayerViewController *playerVC; 35 | 36 | @end 37 | 38 | @implementation WCLRecordVideoVC 39 | 40 | - (void)dealloc { 41 | _recordEngine = nil; 42 | [[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:[_playerVC moviePlayer]]; 43 | } 44 | 45 | - (void)viewWillAppear:(BOOL)animated { 46 | [super viewWillAppear:animated]; 47 | [self.navigationController setNavigationBarHidden:YES animated:YES]; 48 | } 49 | 50 | - (void)viewDidDisappear:(BOOL)animated { 51 | [super viewDidDisappear:animated]; 52 | [self.recordEngine shutdown]; 53 | } 54 | 55 | - (void)viewDidAppear:(BOOL)animated { 56 | [super viewDidAppear:animated]; 57 | if (_recordEngine == nil) { 58 | [self.recordEngine previewLayer].frame = self.view.bounds; 59 | [self.view.layer insertSublayer:[self.recordEngine previewLayer] atIndex:0]; 60 | } 61 | [self.recordEngine startUp]; 62 | } 63 | 64 | - (void)viewDidLoad { 65 | [super viewDidLoad]; 66 | // Do any additional setup after loading the view from its nib. 67 | // 博客地址:http://blog.csdn.net/wang631106979/article/details/51498009 68 | self.allowRecord = YES; 69 | } 70 | 71 | //根据状态调整view的展示情况 72 | - (void)adjustViewFrame { 73 | [self.view layoutIfNeeded]; 74 | [UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 75 | if (self.recordBt.selected) { 76 | self.topViewTop.constant = -64; 77 | [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide]; 78 | }else { 79 | [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide]; 80 | self.topViewTop.constant = 0; 81 | } 82 | if (self.videoStyle == VideoRecord) { 83 | self.locationVideoBT.alpha = 0; 84 | } 85 | [self.view layoutIfNeeded]; 86 | } completion:nil]; 87 | } 88 | 89 | #pragma mark - set、get方法 90 | - (WCLRecordEngine *)recordEngine { 91 | if (_recordEngine == nil) { 92 | _recordEngine = [[WCLRecordEngine alloc] init]; 93 | _recordEngine.delegate = self; 94 | } 95 | return _recordEngine; 96 | } 97 | 98 | - (UIImagePickerController *)moviePicker { 99 | if (_moviePicker == nil) { 100 | _moviePicker = [[UIImagePickerController alloc] init]; 101 | _moviePicker.delegate = self; 102 | _moviePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; 103 | _moviePicker.mediaTypes = @[(NSString *)kUTTypeMovie]; 104 | } 105 | return _moviePicker; 106 | } 107 | 108 | #pragma mark - Apple相册选择代理 109 | //选择了某个照片的回调函数/代理回调 110 | - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { 111 | if ([[info objectForKey:UIImagePickerControllerMediaType] isEqualToString:(NSString*)kUTTypeMovie]) { 112 | //获取视频的名称 113 | NSString * videoPath=[NSString stringWithFormat:@"%@",[info objectForKey:UIImagePickerControllerMediaURL]]; 114 | NSRange range =[videoPath rangeOfString:@"trim."];//匹配得到的下标 115 | NSString *content=[videoPath substringFromIndex:range.location+5]; 116 | //视频的后缀 117 | NSRange rangeSuffix=[content rangeOfString:@"."]; 118 | NSString * suffixName=[content substringFromIndex:rangeSuffix.location+1]; 119 | //如果视频是mov格式的则转为MP4的 120 | if ([suffixName isEqualToString:@"MOV"]) { 121 | NSURL *videoUrl = [info objectForKey:UIImagePickerControllerMediaURL]; 122 | __weak typeof(self) weakSelf = self; 123 | [self.recordEngine changeMovToMp4:videoUrl dataBlock:^(UIImage *movieImage) { 124 | 125 | [weakSelf.moviePicker dismissViewControllerAnimated:YES completion:^{ 126 | weakSelf.playerVC = [[MPMoviePlayerViewController alloc] initWithContentURL:[NSURL fileURLWithPath:weakSelf.recordEngine.videoPath]]; 127 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playVideoFinished:) name:MPMoviePlayerPlaybackDidFinishNotification object:[weakSelf.playerVC moviePlayer]]; 128 | [[weakSelf.playerVC moviePlayer] prepareToPlay]; 129 | 130 | [weakSelf presentMoviePlayerViewControllerAnimated:weakSelf.playerVC]; 131 | [[weakSelf.playerVC moviePlayer] play]; 132 | }]; 133 | }]; 134 | } 135 | } 136 | } 137 | 138 | #pragma mark - WCLRecordEngineDelegate 139 | - (void)recordProgress:(CGFloat)progress { 140 | if (progress >= 1) { 141 | [self recordAction:self.recordBt]; 142 | self.allowRecord = NO; 143 | } 144 | self.progressView.progress = progress; 145 | } 146 | 147 | #pragma mark - 各种点击事件 148 | //返回点击事件 149 | - (IBAction)dismissAction:(id)sender { 150 | [self.navigationController popViewControllerAnimated:YES]; 151 | } 152 | 153 | //开关闪光灯 154 | - (IBAction)flashLightAction:(id)sender { 155 | if (self.changeCameraBT.selected == NO) { 156 | self.flashLightBT.selected = !self.flashLightBT.selected; 157 | if (self.flashLightBT.selected == YES) { 158 | [self.recordEngine openFlashLight]; 159 | }else { 160 | [self.recordEngine closeFlashLight]; 161 | } 162 | } 163 | } 164 | 165 | //切换前后摄像头 166 | - (IBAction)changeCameraAction:(id)sender { 167 | self.changeCameraBT.selected = !self.changeCameraBT.selected; 168 | if (self.changeCameraBT.selected == YES) { 169 | //前置摄像头 170 | [self.recordEngine closeFlashLight]; 171 | self.flashLightBT.selected = NO; 172 | [self.recordEngine changeCameraInputDeviceisFront:YES]; 173 | }else { 174 | [self.recordEngine changeCameraInputDeviceisFront:NO]; 175 | } 176 | } 177 | 178 | //录制下一步点击事件 179 | - (IBAction)recordNextAction:(id)sender { 180 | if (_recordEngine.videoPath.length > 0) { 181 | __weak typeof(self) weakSelf = self; 182 | [self.recordEngine stopCaptureHandler:^(UIImage *movieImage) { 183 | weakSelf.playerVC = [[MPMoviePlayerViewController alloc] initWithContentURL:[NSURL fileURLWithPath:weakSelf.recordEngine.videoPath]]; 184 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playVideoFinished:) name:MPMoviePlayerPlaybackDidFinishNotification object:[weakSelf.playerVC moviePlayer]]; 185 | [[weakSelf.playerVC moviePlayer] prepareToPlay]; 186 | 187 | [weakSelf presentMoviePlayerViewControllerAnimated:weakSelf.playerVC]; 188 | [[weakSelf.playerVC moviePlayer] play]; 189 | }]; 190 | }else { 191 | NSLog(@"请先录制视频~"); 192 | } 193 | } 194 | 195 | //当点击Done按键或者播放完毕时调用此函数 196 | - (void) playVideoFinished:(NSNotification *)theNotification { 197 | MPMoviePlayerController *player = [theNotification object]; 198 | [[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:player]; 199 | [player stop]; 200 | [self.playerVC dismissMoviePlayerViewControllerAnimated]; 201 | self.playerVC = nil; 202 | } 203 | 204 | //本地视频点击视频 205 | - (IBAction)locationVideoAction:(id)sender { 206 | self.videoStyle = VideoLocation; 207 | [self.recordEngine shutdown]; 208 | [self presentViewController:self.moviePicker animated:YES completion:nil]; 209 | } 210 | 211 | //开始和暂停录制事件 212 | - (IBAction)recordAction:(UIButton *)sender { 213 | if (self.allowRecord) { 214 | self.videoStyle = VideoRecord; 215 | self.recordBt.selected = !self.recordBt.selected; 216 | if (self.recordBt.selected) { 217 | if (self.recordEngine.isCapturing) { 218 | [self.recordEngine resumeCapture]; 219 | }else { 220 | [self.recordEngine startCapture]; 221 | } 222 | }else { 223 | [self.recordEngine pauseCapture]; 224 | } 225 | [self adjustViewFrame]; 226 | } 227 | } 228 | 229 | - (void)didReceiveMemoryWarning { 230 | [super didReceiveMemoryWarning]; 231 | // Dispose of any resources that can be recreated. 232 | } 233 | 234 | /* 235 | #pragma mark - Navigation 236 | 237 | // In a storyboard-based application, you will often want to do a little preparation before navigation 238 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 239 | // Get the new view controller using [segue destinationViewController]. 240 | // Pass the selected object to the new view controller. 241 | } 242 | */ 243 | 244 | @end 245 | -------------------------------------------------------------------------------- /WCLRecordVideo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // WCLRecordVideo 4 | // 5 | // Created by 王崇磊 on 16/5/24. 6 | // Copyright © 2016年 王崇磊. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /wcl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imwcl/WCLRecordVideo/679ebbe98722d0f27cb1bdf72a11f8e6865b7834/wcl.gif --------------------------------------------------------------------------------