Compare commits

...

23 Commits

Author SHA1 Message Date
chenliming d7164f8b76 update... 2016-08-01 13:52:51 +08:00
chenliming fb7ad31bf0 update.... 2016-08-01 13:49:59 +08:00
chenliming ec61b1f7ec update.... 2016-08-01 13:27:41 +08:00
chenliming ba1280af3a update... 2016-08-01 13:25:03 +08:00
chenliming 9e99357113 update。。。 2016-08-01 12:28:52 +08:00
chenliming 8057a3e013 update.. 2016-08-01 12:16:37 +08:00
chenliming 981b0dd2cd update... 2016-08-01 12:13:57 +08:00
chenliming d93d4b9bf5 support carthage 2016-08-01 11:50:05 +08:00
chenliming df6164fed1 modify podspec 2016-08-01 11:24:19 +08:00
chenliming ba19663059 update version 2016-08-01 11:22:16 +08:00
chenliming 0967800b23 update read 2016-08-01 11:01:55 +08:00
chenliming d0ef23d887 modify readme 2016-08-01 10:59:57 +08:00
chenliming 807d83c263 update podspec 2016-07-29 17:48:54 +08:00
chenliming 28d20814f6 update version 2016-07-29 17:43:33 +08:00
chenliming 4c385f6e89 compile question 2016-07-29 17:38:16 +08:00
chenliming 8708e04c0d modify cropSize 2016-07-29 17:08:45 +08:00
chenliming e47f3154a8 modify black screen bug 2016-07-26 18:27:16 +08:00
chenliming 7062cdcf7c modify bug https://github.com/LaiFengiOS/LFLiveKit/issues/11#issuecomment-235147191 2016-07-26 11:20:34 +08:00
chenliming bbe11292de modify bug https://github.com/LaiFengiOS/LFLiveKit/issues/22 2016-07-25 23:44:12 +08:00
小歪~~~ 2b51c69007 Merge pull request #21 from toss156/master
add LFH264VideoEncoder for iOS7
2016-07-25 15:25:35 +08:00
toss156 eb0edf85d2 Merge branch 'master' of https://github.com/toss156/LFLiveKit
# By chenliming (6) and bunnyirsa (1)
# Via GitHub (4) and chenliming (1)
* 'master' of https://github.com/toss156/LFLiveKit:
  update version
  add Note
  modify rtmp Continues to send after completion
  pod need 0.39,if need 1.0,please open pod file modify
  modify Weak network memory Has been increased
  修复首帧音视频不同步问题
  Modify Click the stop button to continue reconnection

Conflicts:
	LFLiveKit.xcodeproj/project.pbxproj
	LFLiveKit/LFLiveSession.m
	LFLiveKit/publish/LFStreamRTMPSocket.m
2016-07-25 15:18:57 +08:00
feng 5b097680f9 Merge pull request #6 from LaiFengiOS/master
update to last version
2016-07-25 15:01:40 +08:00
toss156 fb9ecaabad add LFH264VideoEncoder for iOS7 2016-07-25 15:00:24 +08:00
35 changed files with 1879 additions and 162 deletions
+22 -1
View File
@@ -1,4 +1,25 @@
# infer
infer-out/
#CocoaPods
# Xcode
.DS_Store
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
*.xcworkspace
!default.xcworkspace
xcuserdata
profile
*.moved-aside
DerivedData
.idea/
# CocoaPods
Pods/
Podfile.lock
+6 -8
View File
@@ -2,24 +2,22 @@
Pod::Spec.new do |s|
s.name = "LFLiveKit"
s.version = "1.9.0"
s.version = "1.9.4"
s.summary = "LaiFeng ios Live. LFLiveKit."
s.homepage = "https://github.com/chenliming777"
s.license = { :type => "MIT", :file => "LICENSE" }
s.author = { "chenliming" => "chenliming777@qq.com" }
s.platform = :ios, "8.0"
s.ios.deployment_target = "8.0"
s.platform = :ios, "7.0"
s.ios.deployment_target = "7.0"
s.source = { :git => "https://github.com/LaiFengiOS/LFLiveKit.git", :tag => "#{s.version}" }
s.source_files = "LFLiveKit/**/*.{h,m}"
s.source_files = "LFLiveKit/**/*.{h,m,mm,cpp,c}"
s.public_header_files = "LFLiveKit/**/*.h"
s.frameworks = "VideoToolbox", "AudioToolbox","AVFoundation","Foundation","UIKit"
s.library = "z"
s.libraries = "c++", "z"
s.requires_arc = true
s.dependency 'LMGPUImage', '~> 0.1.9'
s.dependency "pili-librtmp", "~> 1.0.3"
s.dependency "YYDispatchQueuePool"
s.dependency 'pili-librtmp', '~> 1.0.3.1'
end
+101 -90
View File
@@ -30,15 +30,15 @@
84001FE21D0016380026C63F /* LFGPUImageEmptyFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 84001FB91D0016380026C63F /* LFGPUImageEmptyFilter.m */; };
84001FE31D0016380026C63F /* LFLiveSession.h in Headers */ = {isa = PBXBuildFile; fileRef = 84001FBA1D0016380026C63F /* LFLiveSession.h */; settings = {ATTRIBUTES = (Public, ); }; };
84001FE41D0016380026C63F /* LFLiveSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 84001FBB1D0016380026C63F /* LFLiveSession.m */; };
84001FE51D0016380026C63F /* LFAudioFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 84001FBD1D0016380026C63F /* LFAudioFrame.h */; };
84001FE51D0016380026C63F /* LFAudioFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 84001FBD1D0016380026C63F /* LFAudioFrame.h */; settings = {ATTRIBUTES = (Public, ); }; };
84001FE61D0016380026C63F /* LFAudioFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 84001FBE1D0016380026C63F /* LFAudioFrame.m */; };
84001FE71D0016380026C63F /* LFFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 84001FBF1D0016380026C63F /* LFFrame.h */; };
84001FE71D0016380026C63F /* LFFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 84001FBF1D0016380026C63F /* LFFrame.h */; settings = {ATTRIBUTES = (Public, ); }; };
84001FE81D0016380026C63F /* LFFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 84001FC01D0016380026C63F /* LFFrame.m */; };
84001FE91D0016380026C63F /* LFLiveDebug.h in Headers */ = {isa = PBXBuildFile; fileRef = 84001FC11D0016380026C63F /* LFLiveDebug.h */; settings = {ATTRIBUTES = (Public, ); }; };
84001FEA1D0016380026C63F /* LFLiveDebug.m in Sources */ = {isa = PBXBuildFile; fileRef = 84001FC21D0016380026C63F /* LFLiveDebug.m */; };
84001FEB1D0016380026C63F /* LFLiveStreamInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 84001FC31D0016380026C63F /* LFLiveStreamInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
84001FEC1D0016380026C63F /* LFLiveStreamInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 84001FC41D0016380026C63F /* LFLiveStreamInfo.m */; };
84001FED1D0016380026C63F /* LFVideoFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 84001FC51D0016380026C63F /* LFVideoFrame.h */; };
84001FED1D0016380026C63F /* LFVideoFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 84001FC51D0016380026C63F /* LFVideoFrame.h */; settings = {ATTRIBUTES = (Public, ); }; };
84001FEE1D0016380026C63F /* LFVideoFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 84001FC61D0016380026C63F /* LFVideoFrame.m */; };
84001FF71D0017590026C63F /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84001FF61D0017590026C63F /* AVFoundation.framework */; };
84001FF91D00175D0026C63F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84001FF81D00175D0026C63F /* Foundation.framework */; };
@@ -54,7 +54,17 @@
B289F1DF1D3DE77F00D9C7A5 /* LFStreamSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = B289F1D81D3DE77F00D9C7A5 /* LFStreamSocket.h */; };
B289F1E01D3DE77F00D9C7A5 /* NSMutableArray+LFAdd.h in Headers */ = {isa = PBXBuildFile; fileRef = B289F1D91D3DE77F00D9C7A5 /* NSMutableArray+LFAdd.h */; };
B289F1E11D3DE77F00D9C7A5 /* NSMutableArray+LFAdd.m in Sources */ = {isa = PBXBuildFile; fileRef = B289F1DA1D3DE77F00D9C7A5 /* NSMutableArray+LFAdd.m */; };
BE55DA79155500CDEF87FB5C /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B5758EB2A15DAA132D8BF380 /* libPods.a */; };
B2CD146D1D45F18B008082E8 /* AVEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = B2CD14621D45F18B008082E8 /* AVEncoder.h */; };
B2CD146E1D45F18B008082E8 /* AVEncoder.mm in Sources */ = {isa = PBXBuildFile; fileRef = B2CD14631D45F18B008082E8 /* AVEncoder.mm */; };
B2CD146F1D45F18B008082E8 /* LICENSE.markdown in Sources */ = {isa = PBXBuildFile; fileRef = B2CD14641D45F18B008082E8 /* LICENSE.markdown */; };
B2CD14701D45F18B008082E8 /* MP4Atom.h in Headers */ = {isa = PBXBuildFile; fileRef = B2CD14651D45F18B008082E8 /* MP4Atom.h */; };
B2CD14711D45F18B008082E8 /* MP4Atom.m in Sources */ = {isa = PBXBuildFile; fileRef = B2CD14661D45F18B008082E8 /* MP4Atom.m */; };
B2CD14721D45F18B008082E8 /* NALUnit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B2CD14671D45F18B008082E8 /* NALUnit.cpp */; };
B2CD14731D45F18B008082E8 /* NALUnit.h in Headers */ = {isa = PBXBuildFile; fileRef = B2CD14681D45F18B008082E8 /* NALUnit.h */; };
B2CD14741D45F18B008082E8 /* VideoEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = B2CD14691D45F18B008082E8 /* VideoEncoder.h */; };
B2CD14751D45F18B008082E8 /* VideoEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = B2CD146A1D45F18B008082E8 /* VideoEncoder.m */; };
B2CD14761D45F18B008082E8 /* LFH264VideoEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = B2CD146B1D45F18B008082E8 /* LFH264VideoEncoder.h */; };
B2CD14771D45F18B008082E8 /* LFH264VideoEncoder.mm in Sources */ = {isa = PBXBuildFile; fileRef = B2CD146C1D45F18B008082E8 /* LFH264VideoEncoder.mm */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -68,6 +78,7 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
0BA5A0CE1F07E1D707F69735 /* Pods-LFLiveKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LFLiveKit.release.xcconfig"; path = "Pods/Target Support Files/Pods-LFLiveKit/Pods-LFLiveKit.release.xcconfig"; sourceTree = "<group>"; };
84001F8A1D0015D10026C63F /* LFLiveKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LFLiveKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
84001F8D1D0015D10026C63F /* LFLiveKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LFLiveKit.h; sourceTree = "<group>"; };
84001F8F1D0015D10026C63F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -110,7 +121,6 @@
84001FFC1D0017680026C63F /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
84001FFE1D00176C0026C63F /* VideoToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VideoToolbox.framework; path = System/Library/Frameworks/VideoToolbox.framework; sourceTree = SDKROOT; };
840020001D0017850026C63F /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
A17586B27CD6843997425CCF /* Pods-LFLiveKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LFLiveKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LFLiveKit/Pods-LFLiveKit.debug.xcconfig"; sourceTree = "<group>"; };
B289F1D41D3DE77F00D9C7A5 /* LFStreamingBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LFStreamingBuffer.h; path = LFLiveKit/publish/LFStreamingBuffer.h; sourceTree = SOURCE_ROOT; };
B289F1D51D3DE77F00D9C7A5 /* LFStreamingBuffer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = LFStreamingBuffer.m; path = LFLiveKit/publish/LFStreamingBuffer.m; sourceTree = SOURCE_ROOT; };
B289F1D61D3DE77F00D9C7A5 /* LFStreamRtmpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LFStreamRtmpSocket.h; path = LFLiveKit/publish/LFStreamRtmpSocket.h; sourceTree = SOURCE_ROOT; };
@@ -118,9 +128,19 @@
B289F1D81D3DE77F00D9C7A5 /* LFStreamSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LFStreamSocket.h; path = LFLiveKit/publish/LFStreamSocket.h; sourceTree = SOURCE_ROOT; };
B289F1D91D3DE77F00D9C7A5 /* NSMutableArray+LFAdd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSMutableArray+LFAdd.h"; path = "LFLiveKit/publish/NSMutableArray+LFAdd.h"; sourceTree = SOURCE_ROOT; };
B289F1DA1D3DE77F00D9C7A5 /* NSMutableArray+LFAdd.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSMutableArray+LFAdd.m"; path = "LFLiveKit/publish/NSMutableArray+LFAdd.m"; sourceTree = SOURCE_ROOT; };
B5758EB2A15DAA132D8BF380 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
B75B965E6B94DE4CBCC82EA7 /* Pods-LFLiveKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LFLiveKit.release.xcconfig"; path = "Pods/Target Support Files/Pods-LFLiveKit/Pods-LFLiveKit.release.xcconfig"; sourceTree = "<group>"; };
B2CD14621D45F18B008082E8 /* AVEncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AVEncoder.h; sourceTree = "<group>"; };
B2CD14631D45F18B008082E8 /* AVEncoder.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AVEncoder.mm; sourceTree = "<group>"; };
B2CD14641D45F18B008082E8 /* LICENSE.markdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.markdown; sourceTree = "<group>"; };
B2CD14651D45F18B008082E8 /* MP4Atom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MP4Atom.h; sourceTree = "<group>"; };
B2CD14661D45F18B008082E8 /* MP4Atom.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MP4Atom.m; sourceTree = "<group>"; };
B2CD14671D45F18B008082E8 /* NALUnit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NALUnit.cpp; sourceTree = "<group>"; };
B2CD14681D45F18B008082E8 /* NALUnit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NALUnit.h; sourceTree = "<group>"; };
B2CD14691D45F18B008082E8 /* VideoEncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VideoEncoder.h; sourceTree = "<group>"; };
B2CD146A1D45F18B008082E8 /* VideoEncoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VideoEncoder.m; sourceTree = "<group>"; };
B2CD146B1D45F18B008082E8 /* LFH264VideoEncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LFH264VideoEncoder.h; sourceTree = "<group>"; };
B2CD146C1D45F18B008082E8 /* LFH264VideoEncoder.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = LFH264VideoEncoder.mm; sourceTree = "<group>"; };
B8CB02D2A92EA1F5A262F154 /* libPods-LFLiveKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-LFLiveKit.a"; sourceTree = BUILT_PRODUCTS_DIR; };
D0BB7E7CE5403C4911E026B9 /* Pods-LFLiveKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LFLiveKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LFLiveKit/Pods-LFLiveKit.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -135,7 +155,6 @@
84001FF91D00175D0026C63F /* Foundation.framework in Frameworks */,
84001FF71D0017590026C63F /* AVFoundation.framework in Frameworks */,
AD7F89B4621A7EFEBEA72D49 /* libPods-LFLiveKit.a in Frameworks */,
BE55DA79155500CDEF87FB5C /* libPods.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -160,19 +179,27 @@
84001FF81D00175D0026C63F /* Foundation.framework */,
84001FF61D0017590026C63F /* AVFoundation.framework */,
B8CB02D2A92EA1F5A262F154 /* libPods-LFLiveKit.a */,
B5758EB2A15DAA132D8BF380 /* libPods.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
4FDA0F424950EEA14E09E312 /* Pods */ = {
isa = PBXGroup;
children = (
D0BB7E7CE5403C4911E026B9 /* Pods-LFLiveKit.debug.xcconfig */,
0BA5A0CE1F07E1D707F69735 /* Pods-LFLiveKit.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
84001F801D0015D10026C63F = {
isa = PBXGroup;
children = (
84001F8C1D0015D10026C63F /* LFLiveKit */,
84001F981D0015D10026C63F /* LFLiveKitTests */,
84001F8B1D0015D10026C63F /* Products */,
EDD4B76A07A6817C79BB4E5C /* Pods */,
0C07D14560B9E91EA1B59306 /* Frameworks */,
4FDA0F424950EEA14E09E312 /* Pods */,
);
sourceTree = "<group>";
};
@@ -225,6 +252,9 @@
84001FA91D0016380026C63F /* coder */ = {
isa = PBXGroup;
children = (
B2CD14611D45F18B008082E8 /* H264 */,
B2CD146B1D45F18B008082E8 /* LFH264VideoEncoder.h */,
B2CD146C1D45F18B008082E8 /* LFH264VideoEncoder.mm */,
84001FAA1D0016380026C63F /* LFAudioEncoding.h */,
84001FAB1D0016380026C63F /* LFHardwareAudioEncoder.h */,
84001FAC1D0016380026C63F /* LFHardwareAudioEncoder.m */,
@@ -289,13 +319,20 @@
path = upload;
sourceTree = "<group>";
};
EDD4B76A07A6817C79BB4E5C /* Pods */ = {
B2CD14611D45F18B008082E8 /* H264 */ = {
isa = PBXGroup;
children = (
A17586B27CD6843997425CCF /* Pods-LFLiveKit.debug.xcconfig */,
B75B965E6B94DE4CBCC82EA7 /* Pods-LFLiveKit.release.xcconfig */,
B2CD14621D45F18B008082E8 /* AVEncoder.h */,
B2CD14631D45F18B008082E8 /* AVEncoder.mm */,
B2CD14641D45F18B008082E8 /* LICENSE.markdown */,
B2CD14651D45F18B008082E8 /* MP4Atom.h */,
B2CD14661D45F18B008082E8 /* MP4Atom.m */,
B2CD14671D45F18B008082E8 /* NALUnit.cpp */,
B2CD14681D45F18B008082E8 /* NALUnit.h */,
B2CD14691D45F18B008082E8 /* VideoEncoder.h */,
B2CD146A1D45F18B008082E8 /* VideoEncoder.m */,
);
name = Pods;
path = H264;
sourceTree = "<group>";
};
/* End PBXGroup section */
@@ -305,14 +342,18 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
84001FE51D0016380026C63F /* LFAudioFrame.h in Headers */,
84001FED1D0016380026C63F /* LFVideoFrame.h in Headers */,
84001FE71D0016380026C63F /* LFFrame.h in Headers */,
84001FDB1D0016380026C63F /* LFLiveAudioConfiguration.h in Headers */,
B289F1DD1D3DE77F00D9C7A5 /* LFStreamRtmpSocket.h in Headers */,
84001FDD1D0016380026C63F /* LFLiveVideoConfiguration.h in Headers */,
B2CD14701D45F18B008082E8 /* MP4Atom.h in Headers */,
84001FE31D0016380026C63F /* LFLiveSession.h in Headers */,
B289F1DB1D3DE77F00D9C7A5 /* LFStreamingBuffer.h in Headers */,
84001FEB1D0016380026C63F /* LFLiveStreamInfo.h in Headers */,
84001FE91D0016380026C63F /* LFLiveDebug.h in Headers */,
84001FE71D0016380026C63F /* LFFrame.h in Headers */,
B2CD14761D45F18B008082E8 /* LFH264VideoEncoder.h in Headers */,
84001FD61D0016380026C63F /* LFHardwareAudioEncoder.h in Headers */,
B289F1E01D3DE77F00D9C7A5 /* NSMutableArray+LFAdd.h in Headers */,
84001FDF1D0016380026C63F /* LFGPUImageBeautyFilter.h in Headers */,
@@ -320,11 +361,12 @@
84001FD11D0016380026C63F /* LFAudioCapture.h in Headers */,
84001FE11D0016380026C63F /* LFGPUImageEmptyFilter.h in Headers */,
84001FDA1D0016380026C63F /* LFVideoEncoding.h in Headers */,
84001FE51D0016380026C63F /* LFAudioFrame.h in Headers */,
84001FED1D0016380026C63F /* LFVideoFrame.h in Headers */,
B2CD14741D45F18B008082E8 /* VideoEncoder.h in Headers */,
B2CD14731D45F18B008082E8 /* NALUnit.h in Headers */,
84001FD81D0016380026C63F /* LFHardwareVideoEncoder.h in Headers */,
B289F1DF1D3DE77F00D9C7A5 /* LFStreamSocket.h in Headers */,
84001FD51D0016380026C63F /* LFAudioEncoding.h in Headers */,
B2CD146D1D45F18B008082E8 /* AVEncoder.h in Headers */,
84001F8E1D0015D10026C63F /* LFLiveKit.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -336,14 +378,10 @@
isa = PBXNativeTarget;
buildConfigurationList = 84001F9E1D0015D10026C63F /* Build configuration list for PBXNativeTarget "LFLiveKit" */;
buildPhases = (
8EE9401DCA9508E918B7FB68 /* 📦 Check Pods Manifest.lock */,
98F2C3F394BD79A6D6B8424F /* Check Pods Manifest.lock */,
84001F851D0015D10026C63F /* Sources */,
84001F861D0015D10026C63F /* Frameworks */,
84001F871D0015D10026C63F /* Headers */,
84001F881D0015D10026C63F /* Resources */,
817C22141AD3F2EB34365AA3 /* 📦 Copy Pods Resources */,
8A5D8B623E50AAC1575D1741 /* Copy Pods Resources */,
);
buildRules = (
);
@@ -424,69 +462,6 @@
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
817C22141AD3F2EB34365AA3 /* 📦 Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "📦 Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LFLiveKit/Pods-LFLiveKit-resources.sh\"\n";
showEnvVarsInLog = 0;
};
8A5D8B623E50AAC1575D1741 /* Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
showEnvVarsInLog = 0;
};
8EE9401DCA9508E918B7FB68 /* 📦 Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "📦 Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
98F2C3F394BD79A6D6B8424F /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
84001F851D0015D10026C63F /* Sources */ = {
isa = PBXSourcesBuildPhase;
@@ -494,14 +469,19 @@
files = (
84001FE21D0016380026C63F /* LFGPUImageEmptyFilter.m in Sources */,
84001FE41D0016380026C63F /* LFLiveSession.m in Sources */,
B2CD14711D45F18B008082E8 /* MP4Atom.m in Sources */,
84001FE61D0016380026C63F /* LFAudioFrame.m in Sources */,
84001FDC1D0016380026C63F /* LFLiveAudioConfiguration.m in Sources */,
84001FD41D0016380026C63F /* LFVideoCapture.m in Sources */,
84001FE81D0016380026C63F /* LFFrame.m in Sources */,
B2CD14721D45F18B008082E8 /* NALUnit.cpp in Sources */,
B289F1DC1D3DE77F00D9C7A5 /* LFStreamingBuffer.m in Sources */,
B289F1E11D3DE77F00D9C7A5 /* NSMutableArray+LFAdd.m in Sources */,
B2CD14771D45F18B008082E8 /* LFH264VideoEncoder.mm in Sources */,
84001FDE1D0016380026C63F /* LFLiveVideoConfiguration.m in Sources */,
84001FD21D0016380026C63F /* LFAudioCapture.m in Sources */,
B2CD14751D45F18B008082E8 /* VideoEncoder.m in Sources */,
B2CD146F1D45F18B008082E8 /* LICENSE.markdown in Sources */,
B289F1DE1D3DE77F00D9C7A5 /* LFStreamRtmpSocket.m in Sources */,
84001FD91D0016380026C63F /* LFHardwareVideoEncoder.m in Sources */,
84001FEC1D0016380026C63F /* LFLiveStreamInfo.m in Sources */,
@@ -509,6 +489,7 @@
84001FEE1D0016380026C63F /* LFVideoFrame.m in Sources */,
84001FD71D0016380026C63F /* LFHardwareAudioEncoder.m in Sources */,
84001FE01D0016380026C63F /* LFGPUImageBeautyFilter.m in Sources */,
B2CD146E1D45F18B008082E8 /* AVEncoder.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -623,39 +604,69 @@
};
84001F9F1D0015D10026C63F /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = A17586B27CD6843997425CCF /* Pods-LFLiveKit.debug.xcconfig */;
baseConfigurationReference = D0BB7E7CE5403C4911E026B9 /* Pods-LFLiveKit.debug.xcconfig */;
buildSettings = {
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_BITCODE = NO;
ENABLE_BITCODE = YES;
INFOPLIST_FILE = LFLiveKit/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/LFLiveKit/publish/libpili-librtmp",
);
OTHER_CFLAGS = (
"$(inherited)",
"-isystem",
"\"${PODS_ROOT}/Headers/Public\"",
"-isystem",
"\"${PODS_ROOT}/Headers/Public/LMGPUImage\"",
"-isystem",
"\"${PODS_ROOT}/Headers/Public/pili-librtmp\"",
"-fembed-bitcode",
);
PRODUCT_BUNDLE_IDENTIFIER = com.youku.LFLiveKit.LFLiveKit;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
USER_HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/**";
};
name = Debug;
};
84001FA01D0015D10026C63F /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = B75B965E6B94DE4CBCC82EA7 /* Pods-LFLiveKit.release.xcconfig */;
baseConfigurationReference = 0BA5A0CE1F07E1D707F69735 /* Pods-LFLiveKit.release.xcconfig */;
buildSettings = {
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_BITCODE = NO;
ENABLE_BITCODE = YES;
INFOPLIST_FILE = LFLiveKit/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/LFLiveKit/publish/libpili-librtmp",
);
OTHER_CFLAGS = (
"$(inherited)",
"-isystem",
"\"${PODS_ROOT}/Headers/Public\"",
"-isystem",
"\"${PODS_ROOT}/Headers/Public/LMGPUImage\"",
"-isystem",
"\"${PODS_ROOT}/Headers/Public/pili-librtmp\"",
"-fembed-bitcode",
);
PRODUCT_BUNDLE_IDENTIFIER = com.youku.LFLiveKit.LFLiveKit;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
USER_HEADER_SEARCH_PATHS = "$(BUILT_PRODUCTS_DIR)/**";
};
name = Release;
};
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:LFLiveKit.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.9.0</string>
<string>1.9.4</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+1
View File
@@ -90,6 +90,7 @@
- (void)stopLive {
self.uploading = NO;
[self.socket stop];
self.socket = nil;
}
#pragma mark -- CaptureDelegate
+3 -3
View File
@@ -46,8 +46,8 @@ NSString *const LFAudioComponentFailedToCreateNotification = @"LFAudioComponentF
NSError *error = nil;
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
[session setMode:AVAudioSessionModeVideoRecording error:&error];
[session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:nil];
if (![session setActive:YES error:&error]) {
[self handleAudioComponentCreationFailure];
@@ -55,7 +55,7 @@ NSString *const LFAudioComponentFailedToCreateNotification = @"LFAudioComponentF
AudioComponentDescription acd;
acd.componentType = kAudioUnitType_Output;
acd.componentSubType = kAudioUnitSubType_RemoteIO;
acd.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
acd.componentManufacturer = kAudioUnitManufacturer_Apple;
acd.componentFlags = 0;
acd.componentFlagsMask = 0;
+46 -16
View File
@@ -33,22 +33,23 @@
- (instancetype)initWithVideoConfiguration:(LFLiveVideoConfiguration *)configuration {
if (self = [super init]) {
_configuration = configuration;
if([self pixelBufferImageSize].width < configuration.videoSize.width || [self pixelBufferImageSize].height < configuration.videoSize.height){
@throw [NSException exceptionWithName:@"当前videoSize大小出错" reason:@"LFLiveVideoConfiguration videoSize error" userInfo:nil];
return nil;
}
_videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:_configuration.avSessionPreset cameraPosition:AVCaptureDevicePositionFront];
UIInterfaceOrientation statusBar = [[UIApplication sharedApplication] statusBarOrientation];
if (configuration.landscape) {
if (statusBar != UIInterfaceOrientationLandscapeLeft && statusBar != UIInterfaceOrientationLandscapeRight) {
NSLog(@"当前设置方向出错");
NSLog(@"当前设置方向出错");
NSLog(@"当前设置方向出错");
@throw [NSException exceptionWithName:@"当前设置方向出错" reason:@"LFLiveVideoConfiguration landscape error" userInfo:nil];
_videoCamera.outputImageOrientation = UIInterfaceOrientationLandscapeLeft;
} else {
_videoCamera.outputImageOrientation = statusBar;
}
} else {
if (statusBar != UIInterfaceOrientationPortrait && statusBar != UIInterfaceOrientationPortraitUpsideDown) {
NSLog(@"当前设置方向出错");
NSLog(@"当前设置方向出错");
NSLog(@"当前设置方向出错");
@throw [NSException exceptionWithName:@"当前设置方向出错" reason:@"LFLiveVideoConfiguration landscape error" userInfo:nil];
_videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
} else {
_videoCamera.outputImageOrientation = statusBar;
@@ -203,7 +204,6 @@
}
- (void)setBeautyFace:(BOOL)beautyFace {
if (_beautyFace == beautyFace) return;
_beautyFace = beautyFace;
[_filter removeAllTargets];
@@ -227,18 +227,18 @@
}];
}
if (_configuration.isClipVideo) {
if (_configuration.landscape) {
_cropfilter = [[GPUImageCropFilter alloc] initWithCropRegion:CGRectMake(0.125, 0, 0.75, 1)];
} else {
_cropfilter = [[GPUImageCropFilter alloc] initWithCropRegion:CGRectMake(0, 0.125, 1, 0.75)];
}
CGSize imageSize = [self pixelBufferImageSize];
CGFloat cropLeft = (imageSize.width - self.configuration.videoSize.width)/2.0/imageSize.width;
CGFloat cropTop = (imageSize.height - self.configuration.videoSize.height)/2.0/imageSize.height;
if(cropLeft == 0 && cropTop == 0){
[_videoCamera addTarget:_filter];
}else{
_cropfilter = [[GPUImageCropFilter alloc] initWithCropRegion:CGRectMake(cropLeft, cropTop, 1 - cropLeft*2, 1 - cropTop*2)];
[_videoCamera addTarget:_cropfilter];
[_cropfilter addTarget:_filter];
} else {
[_videoCamera addTarget:_filter];
}
if (_beautyFace) {
[_filter addTarget:_output];
[_output addTarget:_gpuImageView];
@@ -300,4 +300,34 @@
}
}
#pragma mark --
- (CGSize)pixelBufferImageSize{
CGSize videoSize = CGSizeZero;
switch (self.configuration.sessionPreset) {
case LFCaptureSessionPreset360x640:
{
videoSize = CGSizeMake(480, 640);
}
break;
case LFCaptureSessionPreset540x960:
{
videoSize = CGSizeMake(540, 960);
}
break;
case LFCaptureSessionPreset720x1280:
{
videoSize = CGSizeMake(720, 1280);
}
break;
default:
break;
}
if(self.configuration.landscape){
return CGSizeMake(videoSize.height, videoSize.width);
}
return videoSize;
}
@end
+36
View File
@@ -0,0 +1,36 @@
//
// AVEncoder.h
// Encoder Demo
//
// Created by Geraint Davies on 14/01/2013.
// Copyright (c) 2013 GDCL http://www.gdcl.co.uk/license.htm
//
#import <Foundation/Foundation.h>
#import <AVFoundation/AVAssetWriter.h>
#import <AVFoundation/AVAssetWriterInput.h>
#import <AVFoundation/AVMediaFormat.h>
#import <AVFoundation/AVVideoSettings.h>
#import "sys/stat.h"
#import "VideoEncoder.h"
#import "MP4Atom.h"
typedef int (^encoder_handler_t)(NSArray *data, CMTimeValue ptsValue);
typedef int (^param_handler_t)(NSData *params);
@interface AVEncoder : NSObject
@property (atomic) NSUInteger bitrate;
+ (AVEncoder *)encoderForHeight:(int)height andWidth:(int)width bitrate:(int)bitrate;
- (void)encodeWithBlock:(encoder_handler_t)block onParams:(param_handler_t)paramsHandler;
- (void)encodeFrame:(CMSampleBufferRef)sampleBuffer;
- (void)encodePixelBuffer:(CVPixelBufferRef)pixelBuffer pts:(CMTime)pts;
- (NSData *)getConfigData;
- (void)shutdown;
@property (readonly, atomic) int bitspersecond;
@end
+439
View File
@@ -0,0 +1,439 @@
//
// AVEncoder.m
// Encoder Demo
//
// Created by Geraint Davies on 14/01/2013.
// Copyright (c) 2013 GDCL http://www.gdcl.co.uk/license.htm
//
#import "AVEncoder.h"
#import "NALUnit.h"
static void *AVEncoderContext = &AVEncoderContext;
static unsigned int to_host(unsigned char *p){
return (p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3];
}
#define OUTPUT_FILE_SWITCH_POINT (50 * 1024 * 1024) // 50 MB switch point
#define MAX_FILENAME_INDEX 5 // filenames "capture1.mp4" wraps at capture5.mp4
@interface AVEncoder ()
{
// initial writer, used to obtain SPS/PPS from header
VideoEncoder *_headerWriter;
// main encoder/writer
VideoEncoder *_writer;
// writer output file (input to our extractor) and monitoring
NSFileHandle *_inputFile;
dispatch_queue_t _readQueue;
dispatch_source_t _readSource;
// index of current file name
BOOL _swapping;
int _currentFile;
int _height;
int _width;
// param set data
NSData *_avcC;
int _lengthSize;
// location of mdat
BOOL _foundMDAT;
uint64_t _posMDAT;
int _bytesToNextAtom;
BOOL _needParams;
// tracking if NALU is next frame
int _prev_nal_idc;
int _prev_nal_type;
// array of NSData comprising a single frame. each data is one nalu with no start code
NSMutableArray *_pendingNALU;
// FIFO for frame times
NSMutableArray *_times;
encoder_handler_t _outputBlock;
param_handler_t _paramsBlock;
// estimate bitrate over first second
int _bitspersecond;
CMTimeValue _firstpts;
}
@property (atomic) BOOL bitrateChanged;
@end
@implementation AVEncoder
@synthesize bitspersecond = _bitspersecond;
+ (AVEncoder *)encoderForHeight:(int)height andWidth:(int)width bitrate:(int)bitrate {
AVEncoder *enc = [AVEncoder alloc];
[enc initForHeight:height andWidth:width bitrate:bitrate];
return enc;
}
- (NSString *)makeFilename {
NSString *filename = [NSString stringWithFormat:@"capture%d.mp4", _currentFile];
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
return path;
}
- (void)initForHeight:(int)height andWidth:(int)width bitrate:(int)bitrate {
_height = height;
_width = width;
_bitrate = bitrate;
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"params.mp4"];
_headerWriter = [VideoEncoder encoderForPath:path Height:height andWidth:width bitrate:self.bitrate];
_times = [NSMutableArray arrayWithCapacity:10];
// swap between 3 filenames
_currentFile = 1;
_writer = [VideoEncoder encoderForPath:[self makeFilename] Height:height andWidth:width bitrate:self.bitrate];
[self addObserver:self forKeyPath:NSStringFromSelector(@selector(bitrate)) options:0 context:AVEncoderContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (context == AVEncoderContext && [keyPath isEqualToString:NSStringFromSelector(@selector(bitrate))]) {
self.bitrateChanged = YES;
}
}
- (void)encodeWithBlock:(encoder_handler_t)block onParams:(param_handler_t)paramsHandler {
_outputBlock = block;
_paramsBlock = paramsHandler;
_needParams = YES;
_pendingNALU = nil;
_firstpts = -1;
_bitspersecond = 0;
}
- (BOOL)parseParams:(NSString *)path {
NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:path];
struct stat s;
fstat([file fileDescriptor], &s);
MP4Atom *movie = [MP4Atom atomAt:0 size:s.st_size type:(OSType)('file') inFile:file];
MP4Atom *moov = [movie childOfType:(OSType)('moov') startAt:0];
MP4Atom *trak = nil;
if (moov != nil) {
for (;; ) {
trak = [moov nextChild];
if (trak == nil) {
break;
}
if (trak.type == (OSType)('trak')) {
MP4Atom *tkhd = [trak childOfType:(OSType)('tkhd') startAt:0];
NSData *verflags = [tkhd readAt:0 size:4];
unsigned char *p = (unsigned char *)[verflags bytes];
if (p[3] & 1) {
break;
} else {
tkhd = nil;
}
}
}
}
MP4Atom *stsd = nil;
if (trak != nil) {
MP4Atom *media = [trak childOfType:(OSType)('mdia') startAt:0];
if (media != nil) {
MP4Atom *minf = [media childOfType:(OSType)('minf') startAt:0];
if (minf != nil) {
MP4Atom *stbl = [minf childOfType:(OSType)('stbl') startAt:0];
if (stbl != nil) {
stsd = [stbl childOfType:(OSType)('stsd') startAt:0];
}
}
}
}
if (stsd != nil) {
MP4Atom *avc1 = [stsd childOfType:(OSType)('avc1') startAt:8];
if (avc1 != nil) {
MP4Atom *esd = [avc1 childOfType:(OSType)('avcC') startAt:78];
if (esd != nil) {
// this is the avcC record that we are looking for
_avcC = [esd readAt:0 size:esd.length];
if (_avcC != nil) {
// extract size of length field
unsigned char *p = (unsigned char *)[_avcC bytes];
_lengthSize = (p[4] & 3) + 1;
return YES;
}
}
}
}
return NO;
}
- (void)onParamsCompletion {
// the initial one-frame-only file has been completed
// Extract the avcC structure and then start monitoring the
// main file to extract video from the mdat chunk.
if ([self parseParams:_headerWriter.path]) {
if (_paramsBlock) {
_paramsBlock(_avcC);
}
_headerWriter = nil;
_swapping = NO;
_inputFile = [NSFileHandle fileHandleForReadingAtPath:_writer.path];
_readQueue = dispatch_queue_create("uk.co.gdcl.avencoder.read", DISPATCH_QUEUE_SERIAL);
_readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, [_inputFile fileDescriptor], 0, _readQueue);
dispatch_source_set_event_handler(_readSource, ^{
[self onFileUpdate];
});
dispatch_resume(_readSource);
}
}
- (void)encodeFrame:(CMSampleBufferRef)sampleBuffer {
CMTime prestime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
NSNumber *pts = [NSNumber numberWithLongLong:prestime.value];
@synchronized(self){
if (_needParams) {
// the avcC record is needed for decoding and it's not written to the file until
// completion. We get round that by writing the first frame to two files; the first
// file (containing only one frame) is then finished, so we can extract the avcC record.
// Only when we've got that do we start reading from the main file.
_needParams = NO;
if ([_headerWriter encodeFrame:sampleBuffer]) {
[_headerWriter finishWithCompletionHandler:^{
[self onParamsCompletion];
}];
}
}
}
@synchronized(_times){
[_times addObject:pts];
}
@synchronized(self){
// switch output files when we reach a size limit
// to avoid runaway storage use.
if (!_swapping) {
struct stat st;
fstat([_inputFile fileDescriptor], &st);
if (st.st_size > OUTPUT_FILE_SWITCH_POINT || self.bitrateChanged) {
self.bitrateChanged = NO;
_swapping = YES;
VideoEncoder *oldVideo = _writer;
// construct a new writer to the next filename
if (++_currentFile > MAX_FILENAME_INDEX) {
_currentFile = 1;
}
//NSLog(@"Swap to file %d", _currentFile);
_writer = [VideoEncoder encoderForPath:[self makeFilename] Height:_height andWidth:_width bitrate:self.bitrate];
// to do this seamlessly requires a few steps in the right order
// first, suspend the read source
if (_readSource) {
dispatch_source_cancel(_readSource);
// execute the next step as a block on the same queue, to be sure the suspend is done
dispatch_async(_readQueue, ^{
// finish the file, writing moov, before reading any more from the file
// since we don't yet know where the mdat ends
_readSource = nil;
[oldVideo finishWithCompletionHandler:^{
[self swapFiles:oldVideo.path];
}];
});
} else {
[self swapFiles:oldVideo.path];
}
}
}
[_writer encodeFrame:sampleBuffer];
}
}
- (void)swapFiles:(NSString *)oldPath {
// save current position
uint64_t pos = [_inputFile offsetInFile];
// re-read mdat length
[_inputFile seekToFileOffset:_posMDAT];
NSData *hdr = [_inputFile readDataOfLength:4];
unsigned char *p = (unsigned char *)[hdr bytes];
if (p) {
int lenMDAT = to_host(p);
// extract nalus from saved position to mdat end
uint64_t posEnd = _posMDAT + lenMDAT;
uint32_t cRead = (uint32_t)(posEnd - pos);
[_inputFile seekToFileOffset:pos];
[self readAndDeliver:cRead];
}
// close and remove file
[_inputFile closeFile];
_foundMDAT = false;
_bytesToNextAtom = 0;
[[NSFileManager defaultManager] removeItemAtPath:oldPath error:nil];
// open new file and set up dispatch source
_inputFile = [NSFileHandle fileHandleForReadingAtPath:_writer.path];
_readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, [_inputFile fileDescriptor], 0, _readQueue);
dispatch_source_set_event_handler(_readSource, ^{
[self onFileUpdate];
});
dispatch_resume(_readSource);
_swapping = NO;
}
- (void)readAndDeliver:(uint32_t)cReady {
// Identify the individual NALUs and extract them
while (cReady > _lengthSize) {
NSData *lenField = [_inputFile readDataOfLength:_lengthSize];
cReady -= _lengthSize;
unsigned char *p = (unsigned char *)[lenField bytes];
unsigned int lenNALU = to_host(p);
if (lenNALU > cReady) {
// whole NALU not present -- seek back to start of NALU and wait for more
[_inputFile seekToFileOffset:[_inputFile offsetInFile] - 4];
break;
}
NSData *nalu = [_inputFile readDataOfLength:lenNALU];
cReady -= lenNALU;
[self onNALU:nalu];
}
}
- (void)onFileUpdate {
// called whenever there is more data to read in the main encoder output file.
struct stat s;
fstat([_inputFile fileDescriptor], &s);
int cReady = s.st_size - [_inputFile offsetInFile];
// locate the mdat atom if needed
while (!_foundMDAT && (cReady > 8)) {
if (_bytesToNextAtom == 0) {
NSData *hdr = [_inputFile readDataOfLength:8];
cReady -= 8;
unsigned char *p = (unsigned char *)[hdr bytes];
int lenAtom = to_host(p);
unsigned int nameAtom = to_host(p+4);
if (nameAtom == (unsigned int)('mdat')) {
_foundMDAT = true;
_posMDAT = [_inputFile offsetInFile] - 8;
} else {
_bytesToNextAtom = lenAtom - 8;
}
}
if (_bytesToNextAtom > 0) {
int cThis = cReady < _bytesToNextAtom ? cReady : _bytesToNextAtom;
_bytesToNextAtom -= cThis;
[_inputFile seekToFileOffset:[_inputFile offsetInFile]+cThis];
cReady -= cThis;
}
}
if (!_foundMDAT) {
return;
}
// the mdat must be just encoded video.
[self readAndDeliver:cReady];
}
- (void)onEncodedFrame {
CMTimeValue pts = 0;
@synchronized(_times){
if ([_times count] > 0) {
NSNumber *time = _times[0];
pts = [time longLongValue];
[_times removeObjectAtIndex:0];
if (_firstpts < 0) {
_firstpts = pts;
}
if ((pts - _firstpts) < 1) {
int bytes = 0;
for (NSData *data in _pendingNALU) {
bytes += [data length];
}
_bitspersecond += (bytes * 8);
}
} else {
//NSLog(@"no pts for buffer");
}
}
if (_outputBlock != nil) {
_outputBlock(_pendingNALU, pts);
}
}
// combine multiple NALUs into a single frame, and in the process, convert to BSF
// by adding 00 00 01 startcodes before each NALU.
- (void)onNALU:(NSData *)nalu {
unsigned char *pNal = (unsigned char *)[nalu bytes];
int idc = pNal[0] & 0x60;
int naltype = pNal[0] & 0x1f;
if (_pendingNALU) {
NALUnit nal(pNal, [nalu length]);
// we have existing data —is this the same frame?
// typically there are a couple of NALUs per frame in iOS encoding.
// This is not general-purpose: it assumes that arbitrary slice ordering is not allowed.
BOOL bNew = NO;
if ((idc != _prev_nal_idc) && ((idc * _prev_nal_idc) == 0)) {
bNew = YES;
} else if ((naltype != _prev_nal_type) && ((naltype == 5) || (_prev_nal_type == 5))) {
bNew = YES;
} else if ((naltype >= 1) && (naltype <= 5)) {
nal.Skip(8);
int first_mb = nal.GetUE();
if (first_mb == 0) {
bNew = YES;
}
}
if (bNew) {
[self onEncodedFrame];
_pendingNALU = nil;
}
}
_prev_nal_type = naltype;
_prev_nal_idc = idc;
if (_pendingNALU == nil) {
_pendingNALU = [NSMutableArray arrayWithCapacity:2];
}
[_pendingNALU addObject:nalu];
}
- (NSData *)getConfigData {
return [_avcC copy];
}
- (void)shutdown {
@synchronized(self){
_readSource = nil;
if (_headerWriter) {
[_headerWriter finishWithCompletionHandler:^{
_headerWriter = nil;
}];
}
if (_writer) {
[_writer finishWithCompletionHandler:^{
_writer = nil;
}];
}
// !! wait for these to finish before returning and delete temp files
}
}
@end
+15
View File
@@ -0,0 +1,15 @@
# GDCL Source Code License
Last updated: 20th February 2013
**License Agreement for Source Code provided by GDCL**
This software is supplied to you by Geraint Davies Consulting Ltd ('GDCL') in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this software.
In consideration of your agreement to abide by the following terms, and subject to these terms, GDCL grants you a personal, non-exclusive license, to use, reproduce, modify and redistribute the software, with or without modifications, in source and/or binary forms; provided that if you redistribute the software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the software, and that in all cases attribution of GDCL as the original author of the source code shall be included in all such resulting software products or distributions.
Neither the name, trademarks, service marks or logos of Geraint Davies or GDCL may be used to endorse or promote products derived from the software without specific prior written permission from GDCL. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by GDCL herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the software may be incorporated.
The software is provided by GDCL on an "AS IS" basis. GDCL MAKE NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL GDCL BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF GDCL HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+30
View File
@@ -0,0 +1,30 @@
//
// MP4Atom.h
// Encoder Demo
//
// Created by Geraint Davies on 15/01/2013.
// Copyright (c) 2013 GDCL http://www.gdcl.co.uk/license.htm
//
#import <Foundation/Foundation.h>
@interface MP4Atom : NSObject
{
NSFileHandle *_file;
int64_t _offset;
int64_t _length;
OSType _type;
int64_t _nextChild;
}
@property OSType type;
@property int64_t length;
+ (MP4Atom *)atomAt:(int64_t)offset size:(int)length type:(OSType)fourcc inFile:(NSFileHandle *)handle;
- (BOOL)init:(int64_t)offset size:(int)length type:(OSType)fourcc inFile:(NSFileHandle *)handle;
- (NSData *)readAt:(int64_t)offset size:(int)length;
- (BOOL)setChildOffset:(int64_t)offset;
- (MP4Atom *)nextChild;
- (MP4Atom *)childOfType:(OSType)fourcc startAt:(int64_t)offset;
@end
+90
View File
@@ -0,0 +1,90 @@
//
// MP4Atom.m
// Encoder Demo
//
// Created by Geraint Davies on 15/01/2013.
// Copyright (c) 2013 GDCL http://www.gdcl.co.uk/license.htm
//
#import "MP4Atom.h"
static unsigned int to_host(unsigned char *p){
return (p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3];
}
@implementation MP4Atom
@synthesize type = _type;
@synthesize length = _length;
+ (MP4Atom *)atomAt:(int64_t)offset size:(int)length type:(OSType)fourcc inFile:(NSFileHandle *)handle {
MP4Atom *atom = [MP4Atom alloc];
if (![atom init:offset size:length type:fourcc inFile:handle]) {
return nil;
}
return atom;
}
- (BOOL)init:(int64_t)offset size:(int)length type:(OSType)fourcc inFile:(NSFileHandle *)handle {
_file = handle;
_offset = offset;
_length = length;
_type = fourcc;
_nextChild = 0;
return YES;
}
- (NSData *)readAt:(int64_t)offset size:(int)length {
[_file seekToFileOffset:_offset + offset];
return [_file readDataOfLength:length];
}
- (BOOL)setChildOffset:(int64_t)offset {
_nextChild = offset;
return YES;
}
- (MP4Atom *)nextChild {
if (_nextChild <= (_length - 8)) {
[_file seekToFileOffset:_offset + _nextChild];
NSData *data = [_file readDataOfLength:8];
int cHeader = 8;
unsigned char *p = (unsigned char *)[data bytes];
int64_t len = to_host(p);
OSType fourcc = to_host(p + 4);
if (len == 1) {
// 64-bit extended length
cHeader += 8;
data = [_file readDataOfLength:8];
p = (unsigned char *)[data bytes];
len = to_host(p);
len = (len << 32) + to_host(p + 4);
} else if (len == 0) {
// whole remaining parent space
len = _length - _nextChild;
}
if (fourcc == (OSType)('uuid')) {
cHeader += 16;
}
if ((len < 0) || ((len + _nextChild) > _length)) {
return nil;
}
int64_t offset = _nextChild + cHeader;
_nextChild += len;
len -= cHeader;
return [MP4Atom atomAt:offset+_offset size:len type:fourcc inFile:_file];
}
return nil;
}
- (MP4Atom *)childOfType:(OSType)fourcc startAt:(int64_t)offset {
[self setChildOffset:offset];
MP4Atom *child = nil;
do {
child = [self nextChild];
} while ((child != nil) && (child.type != fourcc));
return child;
}
@end
+424
View File
@@ -0,0 +1,424 @@
//
// NALUnit.cpp
//
// Implementation of Basic parsing of H.264 NAL Units
//
// Geraint Davies, March 2004
//
// Copyright (c) GDCL 2004-2008 http://www.gdcl.co.uk/license.htm
#include "NALUnit.h"
// --- core NAL Unit implementation ------------------------------
NALUnit::NALUnit()
: m_pStart(NULL),
m_cBytes(0){
}
bool
NALUnit::GetStartCode(const BYTE *& pBegin, const BYTE *& pStart, int& cRemain){
// start code is any number of 00 followed by 00 00 01
// We need to record the first 00 in pBegin and the first byte
// following the startcode in pStart.
// if no start code is found, pStart and cRemain should be unchanged.
const BYTE *pThis = pStart;
int cBytes = cRemain;
pBegin = NULL;
while (cBytes >= 4) {
if (pThis[0] == 0) {
// remember first 00
if (pBegin == NULL) {
pBegin = pThis;
}
if ((pThis[1] == 0) &&
(pThis[2] == 1)) {
// point to type byte of NAL unit
pStart = pThis + 3;
cRemain = cBytes - 3;
return true;
}
} else {
pBegin = NULL;
}
cBytes--;
pThis++;
}
return false;
}
bool
NALUnit::Parse(const BYTE *pBuffer, int cSpace, int LengthSize, bool bEnd){
// if we get the start code but not the whole
// NALU, we can return false but still have the length property valid
m_cBytes = 0;
ResetBitstream();
if (LengthSize > 0) {
m_pStartCodeStart = pBuffer;
if (LengthSize > cSpace) {
return false;
}
m_cBytes = 0;
for (int i = 0; i < LengthSize; i++) {
m_cBytes <<= 8;
m_cBytes += *pBuffer++;
}
if ((m_cBytes+LengthSize) <= cSpace) {
m_pStart = pBuffer;
return true;
}
} else {
// this is not length-delimited: we must look for start codes
const BYTE *pBegin;
if (GetStartCode(pBegin, pBuffer, cSpace)) {
m_pStart = pBuffer;
m_pStartCodeStart = pBegin;
// either we find another startcode, or we continue to the
// buffer end (if this is the last block of data)
if (GetStartCode(pBegin, pBuffer, cSpace)) {
m_cBytes = int(pBegin - m_pStart);
return true;
} else if (bEnd) {
// current element extends to end of buffer
m_cBytes = cSpace;
return true;
}
}
}
return false;
}
// bitwise access to data
void
NALUnit::ResetBitstream(){
m_idx = 0;
m_nBits = 0;
m_cZeros = 0;
}
void
NALUnit::Skip(int nBits){
if (nBits < m_nBits) {
m_nBits -= nBits;
} else {
nBits -= m_nBits;
while (nBits >= 8) {
GetBYTE();
nBits -= 8;
}
if (nBits) {
m_byte = GetBYTE();
m_nBits = 8;
m_nBits -= nBits;
}
}
}
// get the next byte, removing emulation prevention bytes
BYTE
NALUnit::GetBYTE(){
if (m_idx >= m_cBytes) {
return 0;
}
BYTE b = m_pStart[m_idx++];
// to avoid start-code emulation, a byte 0x03 is inserted
// after any 00 00 pair. Discard that here.
if (b == 0) {
m_cZeros++;
if ((m_idx < m_cBytes) && (m_cZeros == 2) && (m_pStart[m_idx] == 0x03)) {
m_idx++;
m_cZeros = 0;
}
} else {
m_cZeros = 0;
}
return b;
}
unsigned long
NALUnit::GetBit(){
if (m_nBits == 0) {
m_byte = GetBYTE();
m_nBits = 8;
}
m_nBits--;
return (m_byte >> m_nBits) & 0x1;
}
unsigned long
NALUnit::GetWord(int nBits){
unsigned long u = 0;
while (nBits > 0) {
u <<= 1;
u |= GetBit();
nBits--;
}
return u;
}
unsigned long
NALUnit::GetUE(){
// Exp-Golomb entropy coding: leading zeros, then a one, then
// the data bits. The number of leading zeros is the number of
// data bits, counting up from that number of 1s as the base.
// That is, if you see
// 0001010
// You have three leading zeros, so there are three data bits (010)
// counting up from a base of 111: thus 111 + 010 = 1001 = 9
int cZeros = 0;
while (GetBit() == 0) {
cZeros++;
}
return GetWord(cZeros) + ((1 << cZeros)-1);
}
long
NALUnit::GetSE(){
// same as UE but signed.
// basically the unsigned numbers are used as codes to indicate signed numbers in pairs
// in increasing value. Thus the encoded values
// 0, 1, 2, 3, 4
// mean
// 0, 1, -1, 2, -2 etc
unsigned long UE = GetUE();
bool bPositive = UE & 1;
long SE = (UE + 1) >> 1;
if (!bPositive) {
SE = -SE;
}
return SE;
}
// --- sequence params parsing ---------------
SeqParamSet::SeqParamSet()
: m_cx(0),
m_cy(0),
m_FrameBits(0){
// SetRect(&m_rcFrame, 0, 0, 0, 0);
}
void
ScalingList(int size, NALUnit *pnalu){
long lastScale = 8;
long nextScale = 8;
for (int j = 0; j < size; j++) {
if (nextScale != 0) {
long delta = pnalu->GetSE();
nextScale = (lastScale + delta + 256) %256;
}
int scaling_list_j = (nextScale == 0) ? lastScale : nextScale;
lastScale = scaling_list_j;
}
}
bool
SeqParamSet::Parse(NALUnit *pnalu){
if (pnalu->Type() != NALUnit::NAL_Sequence_Params) {
return false;
}
// with the UE/SE type encoding, we must decode all the values
// to get through to the ones we want
pnalu->ResetBitstream();
pnalu->Skip(8); // type
m_Profile = pnalu->GetWord(8);
m_Compatibility = (BYTE)pnalu->GetWord(8);
m_Level = pnalu->GetWord(8);
/*int seq_param_id =*/ pnalu->GetUE();
if ((m_Profile == 100) || (m_Profile == 110) || (m_Profile == 122) || (m_Profile == 144)) {
int chroma_fmt = pnalu->GetUE();
if (chroma_fmt == 3) {
pnalu->Skip(1);
}
/* int bit_depth_luma_minus8 = */ pnalu->GetUE();
/* int bit_depth_chroma_minus8 = */ pnalu->GetUE();
pnalu->Skip(1);
int seq_scaling_matrix_present = pnalu->GetBit();
if (seq_scaling_matrix_present) {
for (int i = 0; i < 8; i++) {
if (pnalu->GetBit()) {
if (i < 6) {
ScalingList(16, pnalu);
} else {
ScalingList(64, pnalu);
}
}
}
}
}
int log2_frame_minus4 = pnalu->GetUE();
m_FrameBits = log2_frame_minus4 + 4;
int POCtype = pnalu->GetUE();
if (POCtype == 0) {
/*int log2_poc_minus4 =*/ pnalu->GetUE();
} else if (POCtype == 1) {
pnalu->Skip(1); // delta always zero
/*int nsp_offset =*/ pnalu->GetSE();
/*int nsp_top_to_bottom = */ pnalu->GetSE();
int num_ref_in_cycle = pnalu->GetUE();
for (int i = 0; i < num_ref_in_cycle; i++) {
/*int sf_offset =*/ pnalu->GetSE();
}
} else if (POCtype != 2) {
return false;
}
// else for POCtype == 2, no additional data in stream
/*int num_ref_frames =*/ pnalu->GetUE();
/*int gaps_allowed =*/ pnalu->GetBit();
int mbs_width = pnalu->GetUE();
int mbs_height = pnalu->GetUE();
m_cx = (mbs_width+1) * 16;
m_cy = (mbs_height+1) * 16;
// smoke test validation of sps
if ((m_cx > 2000) || (m_cy > 2000)) {
return false;
}
// if this is false, then sizes are field sizes and need adjusting
m_bFrameOnly = pnalu->GetBit() ? true : false;
if (!m_bFrameOnly) {
pnalu->Skip(1); // adaptive frame/field
}
pnalu->Skip(1); // direct 8x8
#if 0
SetRect(&m_rcFrame, 0, 0, 0, 0);
bool bCrop = pnalu->GetBit() ? true : false;
if (bCrop) {
// get cropping rect
// store as exclusive, pixel parameters relative to frame
m_rcFrame.left = pnalu->GetUE() * 2;
m_rcFrame.right = pnalu->GetUE() * 2;
m_rcFrame.top = pnalu->GetUE() * 2;
m_rcFrame.bottom = pnalu->GetUE() * 2;
}
if (!IsRectEmpty(&m_rcFrame)) {
m_rcFrame.right = m_cx - m_rcFrame.right;
m_rcFrame.bottom = m_cy - m_rcFrame.bottom;
}
#endif
// adjust rect from 2x2 units to pixels
if (!m_bFrameOnly) {
// adjust heights from field to frame
m_cy *= 2;
#if 0
m_rcFrame.top *= 2;
m_rcFrame.bottom *= 2;
#endif
}
// .. rest are not interesting yet
m_nalu = *pnalu;
return true;
}
// --- slice header --------------------
bool
SliceHeader::Parse(NALUnit *pnalu){
switch (pnalu->Type()) {
case NALUnit::NAL_IDR_Slice:
case NALUnit::NAL_Slice:
case NALUnit::NAL_PartitionA:
// all these begin with a slice header
break;
default:
return false;
}
// slice header has the 1-byte type, then one UE value,
// then the frame number.
pnalu->ResetBitstream();
pnalu->Skip(8); // NALU type
pnalu->GetUE(); // first mb in slice
pnalu->GetUE(); // slice type
pnalu->GetUE(); // pic param set id
m_framenum = pnalu->GetWord(m_nBitsFrame);
return true;
}
// --- SEI ----------------------
SEIMessage::SEIMessage(NALUnit *pnalu){
m_pnalu = pnalu;
const BYTE *p = pnalu->Start();
p++; // nalu type byte
m_type = 0;
while (*p == 0xff) {
m_type += 255;
p++;
}
m_type += *p;
p++;
m_length = 0;
while (*p == 0xff) {
m_type += 255;
p++;
}
m_length += *p;
p++;
m_idxPayload = int(p - m_pnalu->Start());
}
avcCHeader::avcCHeader(const BYTE *header, int cBytes){
if (cBytes < 8) {
return;
}
const BYTE *pEnd = header + cBytes;
int cSeq = header[5] & 0x1f;
header += 6;
for (int i = 0; i < cSeq; i++) {
if ((header+2) > pEnd) {
return;
}
int cThis = (header[0] << 8) + header[1];
header += 2;
if ((header+cThis) > pEnd) {
return;
}
if (i == 0) {
NALUnit n(header, cThis);
m_sps = n;
}
header += cThis;
}
if ((header + 3) >= pEnd) {
return;
}
int cPPS = header[0];
if (cPPS > 0) {
int cThis = (header[1] << 8) + header[2];
header += 3;
NALUnit n(header, cThis);
m_pps = n;
}
}
+242
View File
@@ -0,0 +1,242 @@
//
// NALUnit.h
//
// Basic parsing of H.264 NAL Units
//
// Geraint Davies, March 2004
//
// Copyright (c) GDCL 2004-2008 http://www.gdcl.co.uk/license.htm
#pragma once
typedef unsigned char BYTE;
typedef unsigned long ULONG;
#ifndef NULL
#define NULL 0
#endif
class NALUnit
{
public:
NALUnit();
NALUnit(const BYTE* pStart, int len){
m_pStart = m_pStartCodeStart = pStart;
m_cBytes = len;
ResetBitstream();
}
virtual ~NALUnit() {
}
// assignment copies a pointer into a fixed buffer managed elsewhere. We do not copy the data
NALUnit(const NALUnit &r){
m_pStart = r.m_pStart;
m_cBytes = r.m_cBytes;
ResetBitstream();
}
const NALUnit& operator = (const NALUnit &r)
{
m_pStart = r.m_pStart;
m_cBytes = r.m_cBytes;
ResetBitstream();
return *this;
}
enum eNALType {
NAL_Slice = 1,
NAL_PartitionA = 2,
NAL_PartitionB = 3,
NAL_PartitionC = 4,
NAL_IDR_Slice = 5,
NAL_SEI = 6,
NAL_Sequence_Params = 7,
NAL_Picture_Params = 8,
NAL_AUD = 9,
};
// identify a NAL unit within a buffer.
// If LengthSize is non-zero, it is the number of bytes
// of length field we expect. Otherwise, we expect start-code
// delimiters.
bool Parse(const BYTE *pBuffer, int cSpace, int LengthSize, bool bEnd);
eNALType Type(){
if (m_pStart == NULL) {
return eNALType(0);
}
return eNALType(m_pStart[0] & 0x1F);
}
int Length(){
return m_cBytes;
}
const BYTE *Start(){
return m_pStart;
}
// bitwise access to data
void ResetBitstream();
void Skip(int nBits);
unsigned long GetWord(int nBits);
unsigned long GetUE();
long GetSE();
BYTE GetBYTE();
unsigned long GetBit();
const BYTE *StartCodeStart() {
return m_pStartCodeStart;
}
private:
bool GetStartCode(const BYTE *& pBegin, const BYTE *& pStart, int& cRemain);
private:
const BYTE *m_pStartCodeStart;
const BYTE *m_pStart;
int m_cBytes;
// bitstream access
int m_idx;
int m_nBits;
BYTE m_byte;
int m_cZeros;
};
// simple parser for the Sequence parameter set things that we need
class SeqParamSet
{
public:
SeqParamSet();
bool Parse(NALUnit *pnalu);
int FrameBits(){
return m_FrameBits;
}
long EncodedWidth(){
return m_cx;
}
long EncodedHeight(){
return m_cy;
}
#if 0
long CroppedWidth(){
if (IsRectEmpty(&m_rcFrame)) {
return EncodedWidth();
}
return m_rcFrame.right - m_rcFrame.left;
}
long CroppedHeight(){
if (IsRectEmpty(&m_rcFrame)) {
return EncodedHeight();
}
return m_rcFrame.bottom - m_rcFrame.top;
}
RECT *CropRect(){
return &m_rcFrame;
}
#endif
bool Interlaced(){
return !m_bFrameOnly;
}
unsigned int Profile() {
return m_Profile;
}
unsigned int Level() {
return m_Level;
}
BYTE Compat() {
return m_Compatibility;
}
NALUnit *NALU() {
return &m_nalu;
}
private:
NALUnit m_nalu;
int m_FrameBits;
long m_cx;
long m_cy;
// RECT m_rcFrame;
bool m_bFrameOnly;
int m_Profile;
int m_Level;
BYTE m_Compatibility;
};
// extract frame num from slice headers
class SliceHeader
{
public:
SliceHeader(int nBitsFrame)
: m_framenum(0),
m_nBitsFrame(nBitsFrame){
}
bool Parse(NALUnit *pnalu);
int FrameNum(){
return m_framenum;
}
private:
int m_framenum;
int m_nBitsFrame;
};
// SEI message structure
class SEIMessage
{
public:
SEIMessage(NALUnit* pnalu);
int Type() {
return m_type;
}
int Length() {
return m_length;
}
const BYTE *Payload() {
return m_pnalu->Start() + m_idxPayload;
}
private:
NALUnit *m_pnalu;
int m_type;
int m_length;
int m_idxPayload;
};
// avcC structure from MP4
class avcCHeader
{
public:
avcCHeader(const BYTE* header, int cBytes);
NALUnit *sps() {
return &m_sps;
}
NALUnit *pps() {
return &m_pps;
}
private:
NALUnit m_sps;
NALUnit m_pps;
};
+27
View File
@@ -0,0 +1,27 @@
//
// VideoEncoder.h
// Encoder Demo
//
// Created by Geraint Davies on 14/01/2013.
// Copyright (c) 2013 GDCL http://www.gdcl.co.uk/license.htm
//
#import <Foundation/Foundation.h>
#import "AVFoundation/AVAssetWriter.h"
#import "AVFoundation/AVAssetWriterInput.h"
#import "AVFoundation/AVMediaFormat.h"
#import "AVFoundation/AVVideoSettings.h"
@interface VideoEncoder : NSObject
@property NSString *path;
@property (nonatomic, readonly) NSUInteger bitrate;
+ (VideoEncoder *)encoderForPath:(NSString *)path Height:(int)height andWidth:(int)width bitrate:(int)bitrate;
- (void)initPath:(NSString *)path Height:(int)height andWidth:(int)width bitrate:(int)bitrate;
- (void)finishWithCompletionHandler:(void (^)(void))handler;
- (BOOL)encodeFrame:(CMSampleBufferRef)sampleBuffer;
@end
+76
View File
@@ -0,0 +1,76 @@
//
// VideoEncoder.m
// Encoder Demo
//
// Created by Geraint Davies on 14/01/2013.
// Copyright (c) 2013 GDCL http://www.gdcl.co.uk/license.htm
//
#import "VideoEncoder.h"
@implementation VideoEncoder
{
AVAssetWriter *_writer;
AVAssetWriterInput *_writerInput;
NSString *_path;
}
@synthesize path = _path;
+ (VideoEncoder *)encoderForPath:(NSString *)path Height:(int)height andWidth:(int)width bitrate:(int)bitrate {
VideoEncoder *enc = [VideoEncoder alloc];
[enc initPath:path Height:height andWidth:width bitrate:bitrate];
return enc;
}
- (void)initPath:(NSString *)path Height:(int)height andWidth:(int)width bitrate:(int)bitrate {
self.path = path;
_bitrate = bitrate;
[[NSFileManager defaultManager] removeItemAtPath:self.path error:nil];
NSURL *url = [NSURL fileURLWithPath:self.path];
NSDictionary *settings = @{
AVVideoCodecKey: AVVideoCodecH264,
AVVideoWidthKey: @(width),
AVVideoHeightKey: @(height),
AVVideoCompressionPropertiesKey: @{
AVVideoAverageBitRateKey: @(self.bitrate),
AVVideoMaxKeyFrameIntervalKey: @(30 * 2),
AVVideoProfileLevelKey: AVVideoProfileLevelH264Baseline41,
AVVideoAllowFrameReorderingKey: @NO,
}
};
_writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:settings];
_writerInput.expectsMediaDataInRealTime = YES;
_writer = [AVAssetWriter assetWriterWithURL:url fileType:AVFileTypeQuickTimeMovie error:nil];
[_writer addInput:_writerInput];
}
- (void)finishWithCompletionHandler:(void (^)(void))handler {
if (_writer.status == AVAssetWriterStatusWriting) {
[_writer finishWritingWithCompletionHandler:handler];
}
}
- (BOOL)encodeFrame:(CMSampleBufferRef)sampleBuffer {
if (CMSampleBufferDataIsReady(sampleBuffer)) {
if (_writer.status == AVAssetWriterStatusUnknown) {
CMTime startTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
[_writer startWriting];
[_writer startSessionAtSourceTime:startTime];
}
if (_writer.status == AVAssetWriterStatusFailed) {
//NSLog(@"AVAssetWriterStatusFailed");
return NO;
}
if (_writerInput.readyForMoreMediaData == YES) {
[_writerInput appendSampleBuffer:sampleBuffer];
return YES;
}
}
return NO;
}
@end
+16
View File
@@ -0,0 +1,16 @@
//
// LFH264VideoEncoder
// LFLiveKit
//
// Created by feng on 7/5/16.
// Copyright (c) 2014 zhanqi.tv. All rights reserved.
//
#import "LFVideoEncoding.h"
@interface LFH264VideoEncoder : NSObject <LFVideoEncoding> {
}
- (void)shutdown;
@end
+270
View File
@@ -0,0 +1,270 @@
//
// LFH264VideoEncoder
// LFLiveKit
//
// Created by feng on 7/5/16.
// Copyright (c) 2014 zhanqi.tv. All rights reserved.
//
#import <CoreMedia/CoreMedia.h>
#import <mach/mach_time.h>
#import "NALUnit.h"
#import "AVEncoder.h"
#import "LFH264VideoEncoder.h"
#import "LFVideoFrame.h"
@interface LFH264VideoEncoder() {
FILE *fp;
NSInteger frameCount;
BOOL enabledWriteVideoFile;
}
@property (nonatomic, strong) LFLiveVideoConfiguration *configuration;
@property (nonatomic, weak) id<LFVideoEncodingDelegate> h264Delegate;
@property (nonatomic) BOOL isBackGround;
@property (nonatomic) NSInteger currentVideoBitRate;
@property (nonatomic, strong) dispatch_queue_t sendQueue;
@property (nonatomic, strong) AVEncoder *encoder;
@property (nonatomic, strong) NSData *naluStartCode;
@property (nonatomic, strong) NSMutableData *videoSPSandPPS;
@property (nonatomic, strong) NSMutableData *spsData;
@property (nonatomic, strong) NSMutableData *ppsData;
@property (nonatomic, strong) NSMutableData *sei;
@property (nonatomic) CMTimeScale timescale;
@property (nonatomic, strong) NSMutableArray *orphanedFrames;
@property (nonatomic, strong) NSMutableArray *orphanedSEIFrames;
@property (nonatomic) CMTime lastPTS;
@end
@implementation LFH264VideoEncoder
#pragma mark -- LifeCycle
- (instancetype)initWithVideoStreamConfiguration:(LFLiveVideoConfiguration *)configuration {
if (self = [super init]) {
NSLog(@"USE LF264VideoEncoder");
_configuration = configuration;
[self initCompressionSession];
}
return self;
}
- (void)initCompressionSession{
_sendQueue = dispatch_queue_create("com.youku.laifeng.h264.sendframe", DISPATCH_QUEUE_SERIAL);
[self initializeNALUnitStartCode];
_lastPTS = kCMTimeInvalid;
_timescale = 1000;
frameCount = 0;
#ifdef DEBUG
enabledWriteVideoFile = NO;
[self initForFilePath];
#endif
_encoder = [AVEncoder encoderForHeight:_configuration.videoSize.height andWidth:_configuration.videoSize.width bitrate:_configuration.videoBitRate];
[_encoder encodeWithBlock:^int(NSArray* dataArray, CMTimeValue ptsValue) {
[self incomingVideoFrames:dataArray ptsValue:ptsValue];
return 0;
} onParams:^int(NSData *data) {
[self generateSPSandPPS];
return 0;
}];
}
- (void) initializeNALUnitStartCode {
NSUInteger naluLength = 4;
uint8_t *nalu = (uint8_t*)malloc(naluLength * sizeof(uint8_t));
nalu[0] = 0x00;
nalu[1] = 0x00;
nalu[2] = 0x00;
nalu[3] = 0x01;
_naluStartCode = [NSData dataWithBytesNoCopy:nalu length:naluLength freeWhenDone:YES];
}
- (void) generateSPSandPPS {
NSData* config = _encoder.getConfigData;
if (!config) {
return;
}
avcCHeader avcC((const BYTE*)[config bytes], [config length]);
SeqParamSet seqParams;
seqParams.Parse(avcC.sps());
NSData* spsData = [NSData dataWithBytes:avcC.sps()->Start() length:avcC.sps()->Length()];
NSData *ppsData = [NSData dataWithBytes:avcC.pps()->Start() length:avcC.pps()->Length()];
_spsData = [NSMutableData dataWithCapacity:avcC.sps()->Length()+_naluStartCode.length];
_ppsData = [NSMutableData dataWithCapacity:avcC.pps()->Length()+_naluStartCode.length];
[_spsData appendData:_naluStartCode];
[_spsData appendData:spsData];
[_ppsData appendData:_naluStartCode];
[_ppsData appendData:ppsData];
_videoSPSandPPS = [NSMutableData dataWithCapacity:avcC.sps()->Length() + avcC.pps()->Length() + _naluStartCode.length * 2];
[_videoSPSandPPS appendData:_naluStartCode];
[_videoSPSandPPS appendData:spsData];
[_videoSPSandPPS appendData:_naluStartCode];
[_videoSPSandPPS appendData:ppsData];
}
- (void)setVideoBitRate:(NSInteger)videoBitRate{
_currentVideoBitRate = videoBitRate;
_encoder.bitrate = _currentVideoBitRate;
}
- (NSInteger)videoBitRate{
return _currentVideoBitRate;
}
- (void)setDelegate:(id<LFVideoEncodingDelegate>)delegate{
_h264Delegate = delegate;
}
- (void)encodeVideoData:(CVPixelBufferRef)pixelBuffer timeStamp:(uint64_t)timeStamp {
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
CMVideoFormatDescriptionRef videoInfo = NULL;
CMVideoFormatDescriptionCreateForImageBuffer(NULL, pixelBuffer, &videoInfo);
CMTime frameTime = CMTimeMake(timeStamp, 1000);
CMTime duration = CMTimeMake(1, (int32_t)_configuration.videoFrameRate);
CMSampleTimingInfo timing = {duration, frameTime, kCMTimeInvalid};
CMSampleBufferRef sampleBuffer = NULL;
CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, YES, NULL, NULL, videoInfo, &timing, &sampleBuffer);
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
[_encoder encodeFrame:sampleBuffer];
CFRelease(videoInfo);
CFRelease(sampleBuffer);
frameCount++;
}
- (void)addOrphanedFramesFromArray:(NSArray*)frames {
for (NSData *data in frames) {
unsigned char* pNal = (unsigned char*)[data bytes];
int idc = pNal[0] & 0x60;
int naltype = pNal[0] & 0x1f;
if (idc == 0 && naltype == 6) { // SEI
[self.orphanedSEIFrames addObject:data];
} else {
[self.orphanedFrames addObject:data];
}
}
}
- (void)writeVideoFrames:(NSArray*)frames pts:(CMTime)pts {
NSMutableArray *totalFrames = [NSMutableArray array];
if (self.orphanedSEIFrames.count > 0) {
[totalFrames addObjectsFromArray:self.orphanedSEIFrames];
[self.orphanedSEIFrames removeAllObjects];
}
[totalFrames addObjectsFromArray:frames];
NSMutableData *aggregateFrameData = [NSMutableData data];
BOOL hasKeyframe = NO;
for (NSData *data in totalFrames) {
unsigned char* pNal = (unsigned char*)[data bytes];
int idc = pNal[0] & 0x60;
int naltype = pNal[0] & 0x1f;
NSData *videoData = nil;
if (idc == 0 && naltype == 6) { // SEI
_sei = [NSMutableData dataWithData:data];
continue;
} else if (naltype == 5) { // IDR
hasKeyframe = YES;
NSMutableData *IDRData = [NSMutableData dataWithData:_videoSPSandPPS];
if (_sei) {
[IDRData appendData:_naluStartCode];
[IDRData appendData:_sei];
_sei = nil;
}
[IDRData appendData:_naluStartCode];
[IDRData appendData:data];
videoData = IDRData;
} else {
NSMutableData *regularData = [NSMutableData dataWithData:_naluStartCode];
[regularData appendData:data];
videoData = regularData;
}
[aggregateFrameData appendData:videoData];
LFVideoFrame *videoFrame = [LFVideoFrame new];
const char *dataBuffer = (const char *)aggregateFrameData.bytes;
videoFrame.data = [NSMutableData dataWithBytes:dataBuffer + _naluStartCode.length length:aggregateFrameData.length - _naluStartCode.length];
videoFrame.timestamp = pts.value;
videoFrame.isKeyFrame = (naltype == 5);
videoFrame.sps = _spsData;
videoFrame.pps = _ppsData;
if(self.h264Delegate && [self.h264Delegate respondsToSelector:@selector(videoEncoder:videoFrame:)]){
[self.h264Delegate videoEncoder:self videoFrame:videoFrame];
}
}
if (self->enabledWriteVideoFile) {
fwrite(aggregateFrameData.bytes, 1, aggregateFrameData.length, self->fp);
}
}
- (void) incomingVideoFrames:(NSArray*)frames ptsValue:(CMTimeValue)ptsValue {
if (ptsValue == 0) {
[self addOrphanedFramesFromArray:frames];
return;
}
if (!_videoSPSandPPS) {
[self generateSPSandPPS];
}
CMTime pts = CMTimeMake(ptsValue, _timescale);
if (self.orphanedFrames.count > 0) {
CMTime ptsDiff = CMTimeSubtract(pts, _lastPTS);
NSUInteger orphanedFramesCount = self.orphanedFrames.count;
// NSLog(@"lastPTS before first orphaned frame: %lld", _lastPTS.value);
for (NSData *frame in self.orphanedFrames) {
CMTime fakePTSDiff = CMTimeMultiplyByFloat64(ptsDiff, 1.0/(orphanedFramesCount + 1));
CMTime fakePTS = CMTimeAdd(_lastPTS, fakePTSDiff);
// NSLog(@"orphan frame fakePTS: %lld", fakePTS.value);
[self writeVideoFrames:@[frame] pts:fakePTS];
}
// NSLog(@"pts after orphaned frame: %lld", pts.value);
[self.orphanedFrames removeAllObjects];
}
[self writeVideoFrames:frames pts:pts];
_lastPTS = pts;
}
- (void) dealloc {
[_encoder shutdown];
}
- (void)shutdown {
[_encoder encodeWithBlock:nil onParams:nil];
}
- (void)initForFilePath {
char *path = [self GetFilePathByfileName:"IOSCamDemo.h264"];
NSLog(@"%s", path);
self->fp = fopen(path, "wb");
}
- (char *)GetFilePathByfileName:(char *)filename {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *strName = [NSString stringWithFormat:@"%s", filename];
NSString *writablePath = [documentsDirectory stringByAppendingPathComponent:strName];
NSUInteger len = [writablePath length];
char *filepath = (char *)malloc(sizeof(char) * (len + 1));
[writablePath getCString:filepath maxLength:len + 1 encoding:[NSString defaultCStringEncoding]];
return filepath;
}
@end
+1 -2
View File
@@ -21,6 +21,7 @@
- (instancetype)initWithAudioStreamConfiguration:(LFLiveAudioConfiguration *)configuration {
if (self = [super init]) {
NSLog(@"USE LFHardwareAudioEncoder");
_configuration = configuration;
}
return self;
@@ -108,8 +109,6 @@
}
};
OSStatus result = AudioConverterNewSpecific(&inputFormat, &outputFormat, 2, requestedCodecs, &m_converter);
if (result != noErr) return NO;
return YES;
+2 -1
View File
@@ -30,6 +30,7 @@
#pragma mark -- LifeCycle
- (instancetype)initWithVideoStreamConfiguration:(LFLiveVideoConfiguration *)configuration {
if (self = [super init]) {
NSLog(@"USE LFHardwareVideoEncoder");
_configuration = configuration;
[self initCompressionSession];
@@ -225,7 +226,7 @@ static void VideoCompressonOutputCallback(void *VTref, void *VTFrameRef, OSStatu
}
- (void)initForFilePath {
char *path = [self GetFilePathByfileName:"IOSCamDemo.h264"];
char *path = [self GetFilePathByfileName:"IOSCamDemo_HW.h264"];
NSLog(@"%s", path);
self->fp = fopen(path, "wb");
}
@@ -90,7 +90,4 @@ typedef NS_ENUM (NSUInteger, LFLiveVideoQuality){
///< ≈sde3分辨率
@property (nonatomic, assign, readonly) NSString *avSessionPreset;
///< 是否裁剪
@property (nonatomic, assign, readonly) BOOL isClipVideo;
@end
@@ -213,10 +213,6 @@
return sessionPreset;
}
- (BOOL)isClipVideo {
return self.sessionPreset == LFCaptureSessionPreset360x640 ? YES : NO;
}
#pragma mark -- encoder
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:[NSValue valueWithCGSize:self.videoSize] forKey:@"videoSize"];
@@ -248,7 +244,6 @@
@(self.videoBitRate),
@(self.videoMaxBitRate),
@(self.videoMinBitRate),
@(self.isClipVideo),
self.avSessionPreset,
@(self.sessionPreset),
@(self.landscape), ];
@@ -274,7 +269,6 @@
object.videoBitRate == self.videoBitRate &&
object.videoMaxBitRate == self.videoMaxBitRate &&
object.videoMinBitRate == self.videoMinBitRate &&
object.isClipVideo == self.isClipVideo &&
[object.avSessionPreset isEqualToString:self.avSessionPreset] &&
object.sessionPreset == self.sessionPreset &&
object.landscape == self.landscape;
@@ -297,7 +291,6 @@
[desc appendFormat:@" videoBitRate:%zi", self.videoBitRate];
[desc appendFormat:@" videoMaxBitRate:%zi", self.videoMaxBitRate];
[desc appendFormat:@" videoMinBitRate:%zi", self.videoMinBitRate];
[desc appendFormat:@" isClipVideo:%zi", self.isClipVideo];
[desc appendFormat:@" avSessionPreset:%@", self.avSessionPreset];
[desc appendFormat:@" sessionPreset:%zi", self.sessionPreset];
[desc appendFormat:@" landscape:%zi", self.landscape];
@@ -179,12 +179,12 @@
isa = PBXNativeTarget;
buildConfigurationList = B2D23E911D348F3D00B34CA8 /* Build configuration list for PBXNativeTarget "LFLiveKitDemo" */;
buildPhases = (
64B4341F5B9578C3E50D0608 /* 📦 Check Pods Manifest.lock */,
6A9D2ED37E623D4A31A8D2C9 /* 📦 Check Pods Manifest.lock */,
B2D23E761D348F3D00B34CA8 /* Sources */,
B2D23E771D348F3D00B34CA8 /* Frameworks */,
B2D23E781D348F3D00B34CA8 /* Resources */,
90A9D0B9FC19DB224CE96A04 /* 📦 Embed Pods Frameworks */,
5CD44155361B18A2D8700198 /* 📦 Copy Pods Resources */,
34EEB2C8F5E0D371D13B66CA /* 📦 Copy Pods Resources */,
7336E9C92EDCA6C7449F2624 /* 📦 Embed Pods Frameworks */,
);
buildRules = (
);
@@ -251,7 +251,7 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
5CD44155361B18A2D8700198 /* 📦 Copy Pods Resources */ = {
34EEB2C8F5E0D371D13B66CA /* 📦 Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -266,7 +266,7 @@
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LFLiveKitDemo/Pods-LFLiveKitDemo-resources.sh\"\n";
showEnvVarsInLog = 0;
};
64B4341F5B9578C3E50D0608 /* 📦 Check Pods Manifest.lock */ = {
6A9D2ED37E623D4A31A8D2C9 /* 📦 Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -281,7 +281,7 @@
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
90A9D0B9FC19DB224CE96A04 /* 📦 Embed Pods Frameworks */ = {
7336E9C92EDCA6C7449F2624 /* 📦 Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -448,7 +448,7 @@
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = LFLiveKitDemo/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
@@ -465,7 +465,7 @@
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
HEADER_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = LFLiveKitDemo/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "$(inherited)";
+1 -1
View File
@@ -156,7 +156,7 @@ inline static NSString *formatedSpeed(float bytes, float elapsed_milli) {
/***  默认分辨率368 640 音频:44.1 iphone6以上48 双声道 方向竖屏 ***/
_session = [[LFLiveSession alloc] initWithAudioConfiguration:[LFLiveAudioConfiguration defaultConfiguration] videoConfiguration:[LFLiveVideoConfiguration defaultConfigurationForQuality:LFLiveVideoQuality_Medium2 landscape:NO]];
_session = [[LFLiveSession alloc] initWithAudioConfiguration:[LFLiveAudioConfiguration defaultConfiguration] videoConfiguration:[LFLiveVideoConfiguration defaultConfigurationForQuality:LFLiveVideoQuality_Medium3 landscape:NO]];
/**   自己定制单声道 */
/*
+2 -5
View File
@@ -1,10 +1,7 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios,'8.0'
platform :ios,'7.0'
target 'LFLiveKitDemo' do
pod 'LFLiveKit', path: '../'
end
pod 'LFLiveKit', path: '../'
+1 -1
View File
@@ -1,4 +1,4 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios,'8.0'
platform :ios,'7.0'
pod 'LFLiveKit', path: '../'
+4 -4
View File
@@ -1,10 +1,10 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios,'8.0'
platform :ios,'7.0'
target 'LFLiveKit' do
pod 'LMGPUImage', '~> 0.1.9'
pod 'pili-librtmp', '~> 1.0.3.1'
end
pod 'pili-librtmp', '~> 1.0.3'
pod 'LMGPUImage', '~> 0.1.9'
+15 -1
View File
@@ -1,3 +1,5 @@
LFLiveKit
==============
[![Build Status](https://travis-ci.org/LaiFengiOS/LFLiveKit.svg)](https://travis-ci.org/LaiFengiOS/LFLiveKit)&nbsp;
[![License MIT](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://raw.githubusercontent.com/chenliming777/LFLiveKit/master/LICENSE)&nbsp;
@@ -5,7 +7,6 @@
[![Support](https://img.shields.io/badge/support-ios8%2B-orange.svg)](https://www.apple.com/nl/ios/)&nbsp;
![platform](https://img.shields.io/badge/platform-ios-ff69b4.svg)&nbsp;
## LFLiveKit
**LFLiveKit is a opensource RTMP streaming SDK for iOS.**
@@ -40,6 +41,19 @@
$ pod install
### Manually
1. Download all the files in the `LFLiveKit` subdirectory.
2. Add the source files to your Xcode project.
3. Link with required frameworks:
* UIKit
* Foundation
* AVFoundation
* VideoToolbox
* AudioToolbox
* libz
5. Add `LMGPUImage and pili-librtmp`(static library) to your Xcode project.
## Architecture:
capture: LFAudioCapture and LFVideoCapture