Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d7164f8b76 | |||
| fb7ad31bf0 | |||
| ec61b1f7ec | |||
| ba1280af3a | |||
| 9e99357113 | |||
| 8057a3e013 | |||
| 981b0dd2cd | |||
| d93d4b9bf5 | |||
| df6164fed1 | |||
| ba19663059 | |||
| 0967800b23 | |||
| d0ef23d887 | |||
| 807d83c263 | |||
| 28d20814f6 | |||
| 4c385f6e89 | |||
| 8708e04c0d | |||
| e47f3154a8 | |||
| 7062cdcf7c | |||
| bbe11292de | |||
| 2b51c69007 | |||
| eb0edf85d2 | |||
| 5b097680f9 | |||
| fb9ecaabad |
+22
-1
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Generated
BIN
Binary file not shown.
-10
@@ -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>
|
||||
BIN
Binary file not shown.
@@ -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>
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
- (void)stopLive {
|
||||
self.uploading = NO;
|
||||
[self.socket stop];
|
||||
self.socket = nil;
|
||||
}
|
||||
|
||||
#pragma mark -- CaptureDelegate
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Executable
+36
@@ -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
|
||||
Executable
+439
@@ -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
|
||||
Executable
+15
@@ -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.
|
||||
Executable
+30
@@ -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
|
||||
Executable
+90
@@ -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
|
||||
Executable
+424
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+242
@@ -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;
|
||||
};
|
||||
|
||||
Executable
+27
@@ -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
|
||||
Executable
+76
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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)";
|
||||
|
||||
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
@@ -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]];
|
||||
|
||||
/** 自己定制单声道 */
|
||||
/*
|
||||
|
||||
@@ -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: '../'
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
source 'https://github.com/CocoaPods/Specs.git'
|
||||
platform :ios,'8.0'
|
||||
platform :ios,'7.0'
|
||||
|
||||
pod 'LFLiveKit', path: '../'
|
||||
@@ -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'
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
LFLiveKit
|
||||
==============
|
||||
|
||||
[](https://travis-ci.org/LaiFengiOS/LFLiveKit)
|
||||
[](https://raw.githubusercontent.com/chenliming777/LFLiveKit/master/LICENSE)
|
||||
@@ -5,7 +7,6 @@
|
||||
[](https://www.apple.com/nl/ios/)
|
||||

|
||||
|
||||
## 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
|
||||
|
||||
Reference in New Issue
Block a user