Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 91e72e2211 | |||
| 5cc50b6d47 | |||
| 659dfb81d0 | |||
| 4865bdc508 | |||
| e63026415a | |||
| 21335ed3f0 | |||
| b109289bc1 | |||
| 1e56fc2941 | |||
| 14a3b2eb4b | |||
| 510d796ab1 | |||
| 48dac15d54 | |||
| 13bc9ad1eb | |||
| 1e6e42f2c6 | |||
| ed2d37e6f4 | |||
| fbbf5d9da5 | |||
| 8f57142e5a | |||
| d198058ce3 | |||
| 3ea70b4780 | |||
| 8b0224626a | |||
| 41fe9d1070 | |||
| d7b981c516 | |||
| 09a4627886 | |||
| a140408a6a | |||
| 3539bae752 | |||
| 40b1f7d37f | |||
| 5d9bbd2b5b | |||
| 909b49c8e9 | |||
| aea2fe1a7e | |||
| 3279030e13 | |||
| 135910e1d0 | |||
| 5f893de671 | |||
| 55e896a4b6 | |||
| 759daed2bc | |||
| 392770247c | |||
| c3d506c2f6 | |||
| a60d7c504c | |||
| 9a4270ccb7 | |||
| 270a23218e | |||
| 8aa0091432 | |||
| 037dbdef7a | |||
| f91bb1c889 | |||
| 7101ae842b | |||
| f5f37755df | |||
| 43a82bffbb | |||
| d7af9fb4eb | |||
| d25498162c | |||
| 6f3ab33e8b | |||
| 62d0cee977 | |||
| ba7743d6ee |
@@ -2,15 +2,15 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "LFLiveKit"
|
||||
s.version = "1.5"
|
||||
s.version = "1.7.3"
|
||||
s.summary = "LaiFeng ios Live. LFLiveKit."
|
||||
s.homepage = "https://github.com/chenliming777"
|
||||
s.license = { :type => "MIT", :file => "FILE_LICENSE" }
|
||||
s.license = { :type => "MIT", :file => "LICENSE" }
|
||||
s.author = { "chenliming" => "chenliming777@qq.com" }
|
||||
s.platform = :ios, "8.0"
|
||||
s.ios.deployment_target = "8.0"
|
||||
s.source = { :git => "https://github.com/LaiFengiOS/LFLiveKit", :tag => "#{s.version}" }
|
||||
s.source_files = "LFLiveKit/**/*.{*}"
|
||||
s.source = { :git => "https://github.com/LaiFengiOS/LFLiveKit.git", :tag => "#{s.version}" }
|
||||
s.source_files = "LFLiveKit/**/*.{h,m}"
|
||||
s.public_header_files = "LFLiveKit/**/*.h"
|
||||
|
||||
s.frameworks = "VideoToolbox", "AudioToolbox","AVFoundation","Foundation","UIKit"
|
||||
@@ -20,6 +20,7 @@ Pod::Spec.new do |s|
|
||||
|
||||
s.dependency "CocoaAsyncSocket", "~> 7.4.1"
|
||||
s.dependency 'LMGPUImage', '~> 0.1.9'
|
||||
s.dependency "librtmp-iOS", "~> 1.1.0"
|
||||
s.dependency "pili-librtmp", "~> 1.0.2"
|
||||
s.dependency "YYDispatchQueuePool"
|
||||
|
||||
end
|
||||
|
||||
@@ -40,19 +40,19 @@
|
||||
84001FEC1D0016380026C63F /* LFLiveStreamInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 84001FC41D0016380026C63F /* LFLiveStreamInfo.m */; };
|
||||
84001FED1D0016380026C63F /* LFVideoFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 84001FC51D0016380026C63F /* LFVideoFrame.h */; };
|
||||
84001FEE1D0016380026C63F /* LFVideoFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 84001FC61D0016380026C63F /* LFVideoFrame.m */; };
|
||||
84001FEF1D0016380026C63F /* LFStreamingBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = 84001FCA1D0016380026C63F /* LFStreamingBuffer.h */; };
|
||||
84001FF01D0016380026C63F /* LFStreamingBuffer.m in Sources */ = {isa = PBXBuildFile; fileRef = 84001FCB1D0016380026C63F /* LFStreamingBuffer.m */; };
|
||||
84001FF11D0016380026C63F /* LFStreamRtmpSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 84001FCC1D0016380026C63F /* LFStreamRtmpSocket.h */; };
|
||||
84001FF21D0016380026C63F /* LFStreamRtmpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 84001FCD1D0016380026C63F /* LFStreamRtmpSocket.m */; };
|
||||
84001FF31D0016380026C63F /* LFStreamSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 84001FCE1D0016380026C63F /* LFStreamSocket.h */; };
|
||||
84001FF41D0016380026C63F /* NSMutableArray+LFAdd.h in Headers */ = {isa = PBXBuildFile; fileRef = 84001FCF1D0016380026C63F /* NSMutableArray+LFAdd.h */; };
|
||||
84001FF51D0016380026C63F /* NSMutableArray+LFAdd.m in Sources */ = {isa = PBXBuildFile; fileRef = 84001FD01D0016380026C63F /* NSMutableArray+LFAdd.m */; };
|
||||
84001FF71D0017590026C63F /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84001FF61D0017590026C63F /* AVFoundation.framework */; };
|
||||
84001FF91D00175D0026C63F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84001FF81D00175D0026C63F /* Foundation.framework */; };
|
||||
84001FFB1D0017630026C63F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84001FFA1D0017630026C63F /* UIKit.framework */; };
|
||||
84001FFD1D0017680026C63F /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84001FFC1D0017680026C63F /* AudioToolbox.framework */; };
|
||||
84001FFF1D00176C0026C63F /* VideoToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84001FFE1D00176C0026C63F /* VideoToolbox.framework */; };
|
||||
840020011D0017850026C63F /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 840020001D0017850026C63F /* libz.tbd */; };
|
||||
8493627A1D38B542002C8F13 /* LFStreamingBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = 849362701D38B542002C8F13 /* LFStreamingBuffer.h */; };
|
||||
8493627B1D38B542002C8F13 /* LFStreamingBuffer.m in Sources */ = {isa = PBXBuildFile; fileRef = 849362711D38B542002C8F13 /* LFStreamingBuffer.m */; };
|
||||
8493627C1D38B542002C8F13 /* LFStreamRtmpSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 849362721D38B542002C8F13 /* LFStreamRtmpSocket.h */; };
|
||||
8493627D1D38B542002C8F13 /* LFStreamRtmpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 849362731D38B542002C8F13 /* LFStreamRtmpSocket.m */; };
|
||||
8493627E1D38B542002C8F13 /* LFStreamSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 849362741D38B542002C8F13 /* LFStreamSocket.h */; };
|
||||
849362811D38B542002C8F13 /* NSMutableArray+LFAdd.h in Headers */ = {isa = PBXBuildFile; fileRef = 849362781D38B542002C8F13 /* NSMutableArray+LFAdd.h */; };
|
||||
849362821D38B542002C8F13 /* NSMutableArray+LFAdd.m in Sources */ = {isa = PBXBuildFile; fileRef = 849362791D38B542002C8F13 /* NSMutableArray+LFAdd.m */; };
|
||||
AD7F89B4621A7EFEBEA72D49 /* libPods-LFLiveKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B8CB02D2A92EA1F5A262F154 /* libPods-LFLiveKit.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -103,19 +103,19 @@
|
||||
84001FC41D0016380026C63F /* LFLiveStreamInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LFLiveStreamInfo.m; sourceTree = "<group>"; };
|
||||
84001FC51D0016380026C63F /* LFVideoFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LFVideoFrame.h; sourceTree = "<group>"; };
|
||||
84001FC61D0016380026C63F /* LFVideoFrame.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LFVideoFrame.m; sourceTree = "<group>"; };
|
||||
84001FCA1D0016380026C63F /* LFStreamingBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LFStreamingBuffer.h; sourceTree = "<group>"; };
|
||||
84001FCB1D0016380026C63F /* LFStreamingBuffer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LFStreamingBuffer.m; sourceTree = "<group>"; };
|
||||
84001FCC1D0016380026C63F /* LFStreamRtmpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LFStreamRtmpSocket.h; sourceTree = "<group>"; };
|
||||
84001FCD1D0016380026C63F /* LFStreamRtmpSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LFStreamRtmpSocket.m; sourceTree = "<group>"; };
|
||||
84001FCE1D0016380026C63F /* LFStreamSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LFStreamSocket.h; sourceTree = "<group>"; };
|
||||
84001FCF1D0016380026C63F /* NSMutableArray+LFAdd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableArray+LFAdd.h"; sourceTree = "<group>"; };
|
||||
84001FD01D0016380026C63F /* NSMutableArray+LFAdd.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableArray+LFAdd.m"; sourceTree = "<group>"; };
|
||||
84001FF61D0017590026C63F /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||
84001FF81D00175D0026C63F /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||
84001FFA1D0017630026C63F /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
|
||||
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; };
|
||||
849362701D38B542002C8F13 /* LFStreamingBuffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LFStreamingBuffer.h; sourceTree = "<group>"; };
|
||||
849362711D38B542002C8F13 /* LFStreamingBuffer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LFStreamingBuffer.m; sourceTree = "<group>"; };
|
||||
849362721D38B542002C8F13 /* LFStreamRtmpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LFStreamRtmpSocket.h; sourceTree = "<group>"; };
|
||||
849362731D38B542002C8F13 /* LFStreamRtmpSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LFStreamRtmpSocket.m; sourceTree = "<group>"; };
|
||||
849362741D38B542002C8F13 /* LFStreamSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LFStreamSocket.h; sourceTree = "<group>"; };
|
||||
849362781D38B542002C8F13 /* NSMutableArray+LFAdd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableArray+LFAdd.h"; sourceTree = "<group>"; };
|
||||
849362791D38B542002C8F13 /* NSMutableArray+LFAdd.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableArray+LFAdd.m"; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
B8CB02D2A92EA1F5A262F154 /* libPods-LFLiveKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-LFLiveKit.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -192,7 +192,7 @@
|
||||
84001FA41D0016380026C63F /* capture */,
|
||||
84001FA91D0016380026C63F /* coder */,
|
||||
84001FB51D0016380026C63F /* filter */,
|
||||
84001FC91D0016380026C63F /* publish */,
|
||||
8493626F1D38B542002C8F13 /* publish */,
|
||||
84001F8F1D0015D10026C63F /* Info.plist */,
|
||||
);
|
||||
path = LFLiveKit;
|
||||
@@ -270,19 +270,18 @@
|
||||
path = objects;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
84001FC91D0016380026C63F /* publish */ = {
|
||||
8493626F1D38B542002C8F13 /* publish */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
84001FCE1D0016380026C63F /* LFStreamSocket.h */,
|
||||
84001FCA1D0016380026C63F /* LFStreamingBuffer.h */,
|
||||
84001FCB1D0016380026C63F /* LFStreamingBuffer.m */,
|
||||
84001FCC1D0016380026C63F /* LFStreamRtmpSocket.h */,
|
||||
84001FCD1D0016380026C63F /* LFStreamRtmpSocket.m */,
|
||||
84001FCF1D0016380026C63F /* NSMutableArray+LFAdd.h */,
|
||||
84001FD01D0016380026C63F /* NSMutableArray+LFAdd.m */,
|
||||
849362701D38B542002C8F13 /* LFStreamingBuffer.h */,
|
||||
849362711D38B542002C8F13 /* LFStreamingBuffer.m */,
|
||||
849362721D38B542002C8F13 /* LFStreamRtmpSocket.h */,
|
||||
849362731D38B542002C8F13 /* LFStreamRtmpSocket.m */,
|
||||
849362741D38B542002C8F13 /* LFStreamSocket.h */,
|
||||
849362781D38B542002C8F13 /* NSMutableArray+LFAdd.h */,
|
||||
849362791D38B542002C8F13 /* NSMutableArray+LFAdd.m */,
|
||||
);
|
||||
name = publish;
|
||||
path = upload;
|
||||
path = publish;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EDD4B76A07A6817C79BB4E5C /* Pods */ = {
|
||||
@@ -302,25 +301,25 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
84001FDB1D0016380026C63F /* LFLiveAudioConfiguration.h in Headers */,
|
||||
8493627C1D38B542002C8F13 /* LFStreamRtmpSocket.h in Headers */,
|
||||
84001FDD1D0016380026C63F /* LFLiveVideoConfiguration.h in Headers */,
|
||||
84001FE31D0016380026C63F /* LFLiveSession.h in Headers */,
|
||||
8493627A1D38B542002C8F13 /* LFStreamingBuffer.h in Headers */,
|
||||
84001FEB1D0016380026C63F /* LFLiveStreamInfo.h in Headers */,
|
||||
84001FE91D0016380026C63F /* LFLiveDebug.h in Headers */,
|
||||
84001FF11D0016380026C63F /* LFStreamRtmpSocket.h in Headers */,
|
||||
84001FE71D0016380026C63F /* LFFrame.h in Headers */,
|
||||
84001FEF1D0016380026C63F /* LFStreamingBuffer.h in Headers */,
|
||||
84001FD61D0016380026C63F /* LFHardwareAudioEncoder.h in Headers */,
|
||||
84001FDF1D0016380026C63F /* LFGPUImageBeautyFilter.h in Headers */,
|
||||
84001FD31D0016380026C63F /* LFVideoCapture.h in Headers */,
|
||||
84001FD11D0016380026C63F /* LFAudioCapture.h in Headers */,
|
||||
84001FF41D0016380026C63F /* NSMutableArray+LFAdd.h in Headers */,
|
||||
84001FE11D0016380026C63F /* LFGPUImageEmptyFilter.h in Headers */,
|
||||
84001FDA1D0016380026C63F /* LFVideoEncoding.h in Headers */,
|
||||
84001FE51D0016380026C63F /* LFAudioFrame.h in Headers */,
|
||||
84001FED1D0016380026C63F /* LFVideoFrame.h in Headers */,
|
||||
84001FD81D0016380026C63F /* LFHardwareVideoEncoder.h in Headers */,
|
||||
849362811D38B542002C8F13 /* NSMutableArray+LFAdd.h in Headers */,
|
||||
8493627E1D38B542002C8F13 /* LFStreamSocket.h in Headers */,
|
||||
84001FD51D0016380026C63F /* LFAudioEncoding.h in Headers */,
|
||||
84001FF31D0016380026C63F /* LFStreamSocket.h in Headers */,
|
||||
84001F8E1D0015D10026C63F /* LFLiveKit.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -462,11 +461,11 @@
|
||||
84001FDC1D0016380026C63F /* LFLiveAudioConfiguration.m in Sources */,
|
||||
84001FD41D0016380026C63F /* LFVideoCapture.m in Sources */,
|
||||
84001FE81D0016380026C63F /* LFFrame.m in Sources */,
|
||||
84001FF01D0016380026C63F /* LFStreamingBuffer.m in Sources */,
|
||||
84001FF51D0016380026C63F /* NSMutableArray+LFAdd.m in Sources */,
|
||||
849362821D38B542002C8F13 /* NSMutableArray+LFAdd.m in Sources */,
|
||||
8493627B1D38B542002C8F13 /* LFStreamingBuffer.m in Sources */,
|
||||
84001FDE1D0016380026C63F /* LFLiveVideoConfiguration.m in Sources */,
|
||||
84001FD21D0016380026C63F /* LFAudioCapture.m in Sources */,
|
||||
84001FF21D0016380026C63F /* LFStreamRtmpSocket.m in Sources */,
|
||||
8493627D1D38B542002C8F13 /* LFStreamRtmpSocket.m in Sources */,
|
||||
84001FD91D0016380026C63F /* LFHardwareVideoEncoder.m in Sources */,
|
||||
84001FEC1D0016380026C63F /* LFLiveStreamInfo.m in Sources */,
|
||||
84001FEA1D0016380026C63F /* LFLiveDebug.m in Sources */,
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict>
|
||||
<key>84001F891D0015D10026C63F</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>84001F931D0015D10026C63F</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.5</string>
|
||||
<string>1.7.3</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -15,15 +15,6 @@
|
||||
#import "LFLiveVideoConfiguration.h"
|
||||
#import "LFLiveDebug.h"
|
||||
|
||||
typedef void (^ LFRequestComplete)(_Nullable id info,NSError *_Nullable errorMsg);
|
||||
|
||||
/// 流类型
|
||||
typedef NS_ENUM(NSUInteger, LFLiveType){
|
||||
/// rtmp格式
|
||||
LFLiveRTMP = 0,
|
||||
/// tcp 传输flv格式
|
||||
LFLiveFLV = 1,
|
||||
};
|
||||
|
||||
@class LFLiveSession;
|
||||
@protocol LFLiveSessionDelegate <NSObject>
|
||||
@@ -60,6 +51,21 @@ typedef NS_ENUM(NSUInteger, LFLiveType){
|
||||
/** The beautyFace control capture shader filter empty or beautiy */
|
||||
@property (nonatomic, assign) BOOL beautyFace;
|
||||
|
||||
/** The beautyLevel control beautyFace Level, default 0.5, between 0.0 ~ 1.0 */
|
||||
@property (nonatomic, assign) CGFloat beautyLevel;
|
||||
|
||||
/** The brightLevel control brightness Level, default 0.5, between 0.0 ~ 1.0 */
|
||||
@property (nonatomic, assign) CGFloat brightLevel;
|
||||
|
||||
/** The torch control camera zoom scale default 1.0, between 1.0 ~ 3.0 */
|
||||
@property (nonatomic, assign) CGFloat zoomScale;
|
||||
|
||||
/** The torch control capture flash is on or off */
|
||||
@property (nonatomic, assign) BOOL torch;
|
||||
|
||||
/** The mirror control mirror of front camera is on or off */
|
||||
@property (nonatomic, assign) BOOL mirror;
|
||||
|
||||
/** The muted control callbackAudioData,muted will memset 0.*/
|
||||
@property (nonatomic,assign) BOOL muted;
|
||||
|
||||
@@ -89,7 +95,7 @@ typedef NS_ENUM(NSUInteger, LFLiveType){
|
||||
The designated initializer. Multiple instances with the same configuration will make the
|
||||
capture unstable.
|
||||
*/
|
||||
- (nullable instancetype)initWithAudioConfiguration:(nullable LFLiveAudioConfiguration*)audioConfiguration videoConfiguration:(nullable LFLiveVideoConfiguration*)videoConfiguration liveType:(LFLiveType)liveType NS_DESIGNATED_INITIALIZER;
|
||||
- (nullable instancetype)initWithAudioConfiguration:(nullable LFLiveAudioConfiguration*)audioConfiguration videoConfiguration:(nullable LFLiveVideoConfiguration*)videoConfiguration NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/** The start stream .*/
|
||||
- (void)startLive:(nonnull LFLiveStreamInfo*)streamInfo;
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
#import "LFHardwareVideoEncoder.h"
|
||||
#import "LFHardwareAudioEncoder.h"
|
||||
#import "LFStreamRtmpSocket.h"
|
||||
#import "LFStreamTcpSocket.h"
|
||||
#import "LFLiveStreamInfo.h"
|
||||
#import "LFGPUImageBeautyFilter.h"
|
||||
|
||||
#define LFLiveReportKey @"com.youku.liveSessionReport"
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
{
|
||||
dispatch_semaphore_t _lock;
|
||||
}
|
||||
///流媒体格式
|
||||
@property (nonatomic, assign) LFLiveType liveType;
|
||||
///音频配置
|
||||
@property (nonatomic, strong) LFLiveAudioConfiguration *audioConfiguration;
|
||||
///视频配置
|
||||
@@ -65,12 +63,11 @@
|
||||
@implementation LFLiveSession
|
||||
|
||||
#pragma mark -- LifeCycle
|
||||
- (instancetype)initWithAudioConfiguration:(LFLiveAudioConfiguration *)audioConfiguration videoConfiguration:(LFLiveVideoConfiguration *)videoConfiguration liveType:(LFLiveType)liveType{
|
||||
- (instancetype)initWithAudioConfiguration:(LFLiveAudioConfiguration *)audioConfiguration videoConfiguration:(LFLiveVideoConfiguration *)videoConfiguration{
|
||||
if(!audioConfiguration || !videoConfiguration) @throw [NSException exceptionWithName:@"LFLiveSession init error" reason:@"audioConfiguration or videoConfiguration is nil " userInfo:nil];
|
||||
if(self = [super init]){
|
||||
_audioConfiguration = audioConfiguration;
|
||||
_videoConfiguration = videoConfiguration;
|
||||
_liveType = liveType;
|
||||
_lock = dispatch_semaphore_create(1);
|
||||
}
|
||||
return self;
|
||||
@@ -85,7 +82,8 @@
|
||||
- (void)startLive:(LFLiveStreamInfo*)streamInfo{
|
||||
if(!streamInfo) return;
|
||||
_streamInfo = streamInfo;
|
||||
|
||||
_streamInfo.videoConfiguration = _videoConfiguration;
|
||||
_streamInfo.audioConfiguration = _audioConfiguration;
|
||||
[self.socket start];
|
||||
}
|
||||
|
||||
@@ -152,12 +150,12 @@
|
||||
NSUInteger videoBitRate = [_videoEncoder videoBitRate];
|
||||
if(status == LFLiveBuffferIncrease){
|
||||
if(videoBitRate < _videoConfiguration.videoMaxBitRate){
|
||||
videoBitRate = videoBitRate + 50*1024;
|
||||
videoBitRate = videoBitRate + 50 * 1000;
|
||||
[_videoEncoder setVideoBitRate:videoBitRate];
|
||||
}
|
||||
}else{
|
||||
if(videoBitRate > _videoConfiguration.videoMinBitRate){
|
||||
videoBitRate = videoBitRate - 100*1024;
|
||||
videoBitRate = videoBitRate - 100 * 1000;
|
||||
[_videoEncoder setVideoBitRate:videoBitRate];
|
||||
}
|
||||
}
|
||||
@@ -197,6 +195,46 @@
|
||||
return self.videoCaptureSource.beautyFace;
|
||||
}
|
||||
|
||||
- (void)setBeautyLevel:(CGFloat)beautyLevel {
|
||||
[self.videoCaptureSource setBeautyLevel:beautyLevel];
|
||||
}
|
||||
|
||||
- (CGFloat)beautyLevel {
|
||||
return self.videoCaptureSource.beautyLevel;
|
||||
}
|
||||
|
||||
- (void)setBrightLevel:(CGFloat)brightLevel {
|
||||
[self.videoCaptureSource setBrightLevel:brightLevel];
|
||||
}
|
||||
|
||||
- (CGFloat)brightLevel {
|
||||
return self.videoCaptureSource.brightLevel;
|
||||
}
|
||||
|
||||
- (void)setZoomScale:(CGFloat)zoomScale {
|
||||
[self.videoCaptureSource setZoomScale:zoomScale];
|
||||
}
|
||||
|
||||
- (CGFloat)zoomScale {
|
||||
return self.videoCaptureSource.zoomScale;
|
||||
}
|
||||
|
||||
- (void)setTorch:(BOOL)torch {
|
||||
[self.videoCaptureSource setTorch:torch];
|
||||
}
|
||||
|
||||
- (BOOL)torch {
|
||||
return self.videoCaptureSource.torch;
|
||||
}
|
||||
|
||||
- (void)setMirror:(BOOL)mirror {
|
||||
[self.videoCaptureSource setMirror:mirror];
|
||||
}
|
||||
|
||||
- (BOOL)mirror {
|
||||
return self.videoCaptureSource.mirror;
|
||||
}
|
||||
|
||||
- (void)setMuted:(BOOL)muted{
|
||||
[self.audioCaptureSource setMuted:muted];
|
||||
}
|
||||
@@ -239,11 +277,7 @@
|
||||
|
||||
- (id<LFStreamSocket>)socket{
|
||||
if(!_socket){
|
||||
if(self.liveType == LFLiveRTMP){
|
||||
_socket = [[LFStreamRtmpSocket alloc] initWithStream:self.streamInfo];
|
||||
}else if(self.liveType == LFLiveFLV){
|
||||
_socket = [[LFStreamTcpSocket alloc] initWithStream:self.streamInfo videoSize:self.videoConfiguration.videoSize reconnectInterval:self.reconnectInterval reconnectCount:self.reconnectCount];
|
||||
}
|
||||
_socket = [[LFStreamRtmpSocket alloc] initWithStream:self.streamInfo videoSize:self.videoConfiguration.videoSize reconnectInterval:self.reconnectInterval reconnectCount:self.reconnectCount];
|
||||
[_socket setDelegate:self];
|
||||
}
|
||||
return _socket;
|
||||
@@ -263,8 +297,7 @@
|
||||
_timestamp = NOW;
|
||||
_isFirstFrame = false;
|
||||
currentts = 0;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
currentts = NOW - _timestamp;
|
||||
}
|
||||
dispatch_semaphore_signal(_lock);
|
||||
|
||||
@@ -32,7 +32,7 @@ NSString *const LFAudioComponentFailedToCreateNotification = @"LFAudioComponentF
|
||||
self.taskQueue = dispatch_queue_create("com.youku.Laifeng.audioCapture.Queue", NULL);
|
||||
|
||||
AVAudioSession *session = [AVAudioSession sharedInstance];
|
||||
[session setActive:YES withOptions:kAudioSessionSetActiveFlag_NotifyOthersOnDeactivation error:nil];
|
||||
[session setActive:YES error:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||
selector: @selector(handleRouteChange:)
|
||||
@@ -45,7 +45,7 @@ NSString *const LFAudioComponentFailedToCreateNotification = @"LFAudioComponentF
|
||||
|
||||
NSError *error = nil;
|
||||
|
||||
[session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionMixWithOthers error:nil];
|
||||
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
|
||||
|
||||
[session setMode:AVAudioSessionModeVideoRecording error:&error];
|
||||
|
||||
@@ -86,8 +86,8 @@ NSString *const LFAudioComponentFailedToCreateNotification = @"LFAudioComponentF
|
||||
AURenderCallbackStruct cb;
|
||||
cb.inputProcRefCon = (__bridge void *)(self);
|
||||
cb.inputProc = handleInputBuffer;
|
||||
status = AudioUnitSetProperty(self.componetInstance, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &desc, sizeof(desc));
|
||||
status = AudioUnitSetProperty(self.componetInstance, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &cb, sizeof(cb));
|
||||
AudioUnitSetProperty(self.componetInstance, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &desc, sizeof(desc));
|
||||
AudioUnitSetProperty(self.componetInstance, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &cb, sizeof(cb));
|
||||
|
||||
status = AudioUnitInitialize(self.componetInstance);
|
||||
|
||||
@@ -168,6 +168,8 @@ NSString *const LFAudioComponentFailedToCreateNotification = @"LFAudioComponentF
|
||||
seccReason = @"The reason for the change is unknown.";
|
||||
break;
|
||||
}
|
||||
NSLog(@"handleRouteChange reason is %@",seccReason);
|
||||
|
||||
AVAudioSessionPortDescription *input = [[session.currentRoute.inputs count]?session.currentRoute.inputs:nil objectAtIndex:0];
|
||||
if (input.portType == AVAudioSessionPortHeadsetMic) {
|
||||
|
||||
|
||||
@@ -38,6 +38,21 @@
|
||||
/** The beautyFace control capture shader filter empty or beautiy */
|
||||
@property (nonatomic, assign) BOOL beautyFace;
|
||||
|
||||
/** The torch control capture flash is on or off */
|
||||
@property (nonatomic, assign) BOOL torch;
|
||||
|
||||
/** The mirror control mirror of front camera is on or off */
|
||||
@property (nonatomic, assign) BOOL mirror;
|
||||
|
||||
/** The beautyLevel control beautyFace Level, default 0.5, between 0.0 ~ 1.0 */
|
||||
@property (nonatomic, assign) CGFloat beautyLevel;
|
||||
|
||||
/** The brightLevel control brightness Level, default 0.5, between 0.0 ~ 1.0 */
|
||||
@property (nonatomic, assign) CGFloat brightLevel;
|
||||
|
||||
/** The torch control camera zoom scale default 1.0, between 1.0 ~ 3.0 */
|
||||
@property (nonatomic, assign) CGFloat zoomScale;
|
||||
|
||||
/** The videoFrameRate control videoCapture output data count */
|
||||
@property (nonatomic, assign) NSInteger videoFrameRate;
|
||||
|
||||
|
||||
@@ -14,23 +14,47 @@
|
||||
@interface LFVideoCapture ()
|
||||
|
||||
@property(nonatomic, strong) GPUImageVideoCamera *videoCamera;
|
||||
@property(nonatomic, weak) LFGPUImageBeautyFilter *beautyFilter;
|
||||
@property(nonatomic, strong) GPUImageOutput<GPUImageInput> *filter;
|
||||
@property(nonatomic, strong) GPUImageOutput<GPUImageInput> *emptyFilter;
|
||||
@property(nonatomic, strong) GPUImageOutput<GPUImageInput> *beau;
|
||||
@property(nonatomic, strong) GPUImageOutput<GPUImageInput> *output;
|
||||
@property(nonatomic, strong) GPUImageCropFilter *cropfilter;
|
||||
@property(nonatomic, strong) GPUImageView *gpuImageView;
|
||||
@property (nonatomic, strong) LFLiveVideoConfiguration *configuration;
|
||||
@property(nonatomic, strong) LFLiveVideoConfiguration *configuration;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LFVideoCapture
|
||||
@synthesize torch = _torch;
|
||||
@synthesize beautyLevel = _beautyLevel;
|
||||
@synthesize brightLevel = _brightLevel;
|
||||
@synthesize zoomScale = _zoomScale;
|
||||
|
||||
#pragma mark -- LifeCycle
|
||||
- (instancetype)initWithVideoConfiguration:(LFLiveVideoConfiguration *)configuration{
|
||||
if(self = [super init]){
|
||||
_configuration = configuration;
|
||||
_videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:_configuration.avSessionPreset cameraPosition:AVCaptureDevicePositionFront];
|
||||
_videoCamera.outputImageOrientation = _configuration.orientation;
|
||||
UIInterfaceOrientation statusBar = [[UIApplication sharedApplication] statusBarOrientation];
|
||||
if(configuration.landscape){
|
||||
if(statusBar != UIInterfaceOrientationLandscapeLeft && statusBar != UIInterfaceOrientationLandscapeRight){
|
||||
NSLog(@"当前设置方向出错");
|
||||
NSLog(@"当前设置方向出错");
|
||||
NSLog(@"当前设置方向出错");
|
||||
_videoCamera.outputImageOrientation = UIInterfaceOrientationLandscapeLeft;
|
||||
}else{
|
||||
_videoCamera.outputImageOrientation = statusBar;
|
||||
}
|
||||
}else{
|
||||
if(statusBar != UIInterfaceOrientationPortrait && statusBar != UIInterfaceOrientationPortraitUpsideDown){
|
||||
NSLog(@"当前设置方向出错");
|
||||
NSLog(@"当前设置方向出错");
|
||||
NSLog(@"当前设置方向出错");
|
||||
_videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
|
||||
}else{
|
||||
_videoCamera.outputImageOrientation = statusBar;
|
||||
}
|
||||
}
|
||||
|
||||
_videoCamera.horizontallyMirrorFrontFacingCamera = NO;
|
||||
_videoCamera.horizontallyMirrorRearFacingCamera = NO;
|
||||
_videoCamera.frameRate = (int32_t)_configuration.videoFrameRate;
|
||||
@@ -42,8 +66,11 @@
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterBackground:) name:UIApplicationWillResignActiveNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarChanged:) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil];
|
||||
self.beautyFace = YES;
|
||||
self.beautyLevel = 0.5;
|
||||
self.brightLevel = 0.5;
|
||||
self.zoomScale = 1.0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -80,8 +107,11 @@
|
||||
- (void)setCaptureDevicePosition:(AVCaptureDevicePosition)captureDevicePosition{
|
||||
[_videoCamera rotateCamera];
|
||||
_videoCamera.frameRate = (int32_t)_configuration.videoFrameRate;
|
||||
if(captureDevicePosition == AVCaptureDevicePositionFront) [_gpuImageView setInputRotation:kGPUImageFlipHorizonal atIndex:0];
|
||||
else [_gpuImageView setInputRotation:kGPUImageNoRotation atIndex:0];
|
||||
if (captureDevicePosition == AVCaptureDevicePositionFront) {
|
||||
[_gpuImageView setInputRotation:kGPUImageFlipHorizonal atIndex:0];
|
||||
} else {
|
||||
[_gpuImageView setInputRotation:kGPUImageNoRotation atIndex:0];
|
||||
}
|
||||
}
|
||||
|
||||
- (AVCaptureDevicePosition)captureDevicePosition{
|
||||
@@ -98,68 +128,125 @@
|
||||
return _videoCamera.frameRate;
|
||||
}
|
||||
|
||||
- (void)setTorch:(BOOL)torch {
|
||||
BOOL ret;
|
||||
if(!_videoCamera.captureSession) return;
|
||||
AVCaptureSession* session = (AVCaptureSession*)_videoCamera.captureSession;
|
||||
[session beginConfiguration];
|
||||
if (_videoCamera.inputCamera) {
|
||||
if (_videoCamera.inputCamera.torchAvailable) {
|
||||
NSError* err = nil;
|
||||
if ([_videoCamera.inputCamera lockForConfiguration:&err]) {
|
||||
[_videoCamera.inputCamera setTorchMode:( torch ? AVCaptureTorchModeOn : AVCaptureTorchModeOff ) ];
|
||||
[_videoCamera.inputCamera unlockForConfiguration];
|
||||
ret = (_videoCamera.inputCamera.torchMode == AVCaptureTorchModeOn);
|
||||
} else {
|
||||
NSLog(@"Error while locking device for torch: %@", err);
|
||||
ret = false;
|
||||
}
|
||||
} else {
|
||||
NSLog(@"Torch not available in current camera input");
|
||||
}
|
||||
}
|
||||
[session commitConfiguration];
|
||||
_torch = ret;
|
||||
}
|
||||
- (BOOL)torch {
|
||||
return _videoCamera.inputCamera.torchMode;
|
||||
}
|
||||
- (void)setMirror:(BOOL)mirror {
|
||||
_videoCamera.horizontallyMirrorFrontFacingCamera = mirror;
|
||||
_videoCamera.horizontallyMirrorRearFacingCamera = mirror;
|
||||
}
|
||||
- (BOOL)mirror {
|
||||
return _videoCamera.horizontallyMirrorFrontFacingCamera;
|
||||
}
|
||||
- (void)setBeautyLevel:(CGFloat)beautyLevel {
|
||||
_beautyLevel = beautyLevel;
|
||||
if (_beautyFilter) {
|
||||
[_beautyFilter setBeautyLevel:_beautyLevel];
|
||||
}
|
||||
}
|
||||
- (CGFloat)beautyLevel {
|
||||
return _beautyLevel;
|
||||
}
|
||||
- (void)setBrightLevel:(CGFloat)brightLevel {
|
||||
_brightLevel = brightLevel;
|
||||
if (_beautyFilter) {
|
||||
[_beautyFilter setBrightLevel:brightLevel];
|
||||
}
|
||||
}
|
||||
- (CGFloat)brightLevel {
|
||||
return _brightLevel;
|
||||
}
|
||||
- (void)setZoomScale:(CGFloat)zoomScale {
|
||||
if (self.videoCamera && self.videoCamera.inputCamera) {
|
||||
AVCaptureDevice* device = (AVCaptureDevice*)self.videoCamera.inputCamera;
|
||||
if ([device lockForConfiguration:nil]) {
|
||||
device.videoZoomFactor = zoomScale;
|
||||
[device unlockForConfiguration];
|
||||
_zoomScale = zoomScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (CGFloat)zoomScale {
|
||||
return _zoomScale;
|
||||
}
|
||||
|
||||
- (void)setBeautyFace:(BOOL)beautyFace{
|
||||
if(_beautyFace == beautyFace) return;
|
||||
|
||||
_beautyFace = beautyFace;
|
||||
[_emptyFilter removeAllTargets];
|
||||
[_filter removeAllTargets];
|
||||
[_cropfilter removeAllTargets];
|
||||
[_videoCamera removeAllTargets];
|
||||
[_beau removeAllTargets];
|
||||
|
||||
if(_beautyFace){
|
||||
_filter = [[GPUImageUnsharpMaskFilter alloc] init];
|
||||
[(GPUImageUnsharpMaskFilter*)_filter setIntensity:1.23];
|
||||
[(GPUImageUnsharpMaskFilter*)_filter setBlurRadiusInPixels:13];
|
||||
_beau = [[LFGPUImageBeautyFilter alloc] init];
|
||||
_emptyFilter = [[LFGPUImageEmptyFilter alloc] init];
|
||||
}else{
|
||||
_filter = [[LFGPUImageEmptyFilter alloc] init];
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (beautyFace) {
|
||||
if (_beautyFace) {
|
||||
_output = [[LFGPUImageEmptyFilter alloc] init];
|
||||
_filter = [[LFGPUImageBeautyFilter alloc] init];
|
||||
_beautyFilter = _filter;
|
||||
__weak typeof(self) _self = self;
|
||||
[_emptyFilter setFrameProcessingCompletionBlock:^(GPUImageOutput *output, CMTime time) {
|
||||
[_output setFrameProcessingCompletionBlock:^(GPUImageOutput *output, CMTime time) {
|
||||
[_self processVideo:output];
|
||||
}];
|
||||
} else {
|
||||
_filter = [[LFGPUImageEmptyFilter alloc] init];
|
||||
_beautyFilter = nil;
|
||||
__weak typeof(self) _self = self;
|
||||
[_filter setFrameProcessingCompletionBlock:^(GPUImageOutput *output, CMTime time) {
|
||||
[_self processVideo:output];
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
if(_configuration.isClipVideo){
|
||||
if(_configuration.orientation == UIInterfaceOrientationPortrait || _configuration.orientation == UIInterfaceOrientationPortraitUpsideDown){
|
||||
if (_configuration.isClipVideo) {
|
||||
if (_configuration.landscape){
|
||||
_cropfilter = [[GPUImageCropFilter alloc] initWithCropRegion:CGRectMake(0.125, 0, 0.75, 1)];
|
||||
}else{
|
||||
} else {
|
||||
_cropfilter = [[GPUImageCropFilter alloc] initWithCropRegion:CGRectMake(0, 0.125, 1, 0.75)];
|
||||
}
|
||||
[_videoCamera addTarget:_cropfilter];
|
||||
[_cropfilter addTarget:_filter];
|
||||
}else{
|
||||
} else {
|
||||
[_videoCamera addTarget:_filter];
|
||||
|
||||
}
|
||||
|
||||
if (beautyFace) {
|
||||
[_filter addTarget:_beau];
|
||||
[_beau addTarget:_emptyFilter];
|
||||
[_emptyFilter addTarget:_gpuImageView];
|
||||
if (_beautyFace) {
|
||||
[_filter addTarget:_output];
|
||||
[_output addTarget:_gpuImageView];
|
||||
} else {
|
||||
[_filter addTarget:_gpuImageView];
|
||||
}
|
||||
if(_videoCamera.cameraPosition == AVCaptureDevicePositionFront) [_gpuImageView setInputRotation:kGPUImageFlipHorizonal atIndex:0];
|
||||
else [_gpuImageView setInputRotation:kGPUImageNoRotation atIndex:0];
|
||||
|
||||
if (_videoCamera.cameraPosition == AVCaptureDevicePositionFront) {
|
||||
[_gpuImageView setInputRotation:kGPUImageFlipHorizonal atIndex:0];
|
||||
} else {
|
||||
[_gpuImageView setInputRotation:kGPUImageNoRotation atIndex:0];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -- Custom Method
|
||||
- (void) processVideo:(GPUImageOutput *)output{
|
||||
- (void)processVideo:(GPUImageOutput *)output{
|
||||
__weak typeof(self) _self = self;
|
||||
@autoreleasepool {
|
||||
GPUImageFramebuffer *imageFramebuffer = output.framebufferForOutput;
|
||||
@@ -168,7 +255,7 @@
|
||||
if(pixelBuffer && _self.delegate && [_self.delegate respondsToSelector:@selector(captureOutput:pixelBuffer:)]){
|
||||
[_self.delegate captureOutput:_self pixelBuffer:pixelBuffer];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,4 +274,22 @@
|
||||
[UIApplication sharedApplication].idleTimerDisabled = YES;
|
||||
}
|
||||
|
||||
- (void)statusBarChanged:(NSNotification*)notification{
|
||||
NSLog(@"UIApplicationWillChangeStatusBarOrientationNotification. UserInfo: %@", notification.userInfo);
|
||||
UIInterfaceOrientation statusBar = [[UIApplication sharedApplication] statusBarOrientation];
|
||||
if(_configuration.landscape){
|
||||
if(statusBar == UIInterfaceOrientationLandscapeLeft){
|
||||
self.videoCamera.outputImageOrientation = UIInterfaceOrientationLandscapeRight;
|
||||
}else if(statusBar == UIInterfaceOrientationLandscapeRight){
|
||||
self.videoCamera.outputImageOrientation = UIInterfaceOrientationLandscapeLeft;
|
||||
}
|
||||
}else{
|
||||
if(statusBar == UIInterfaceOrientationPortrait){
|
||||
self.videoCamera.outputImageOrientation = UIInterfaceOrientationPortraitUpsideDown;
|
||||
}else if(statusBar == UIInterfaceOrientationPortraitUpsideDown){
|
||||
self.videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
|
||||
uint32_t count = size / sizeof(AudioClassDescription);
|
||||
AudioClassDescription descs[count];
|
||||
status = AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(encoderSpecifier), &encoderSpecifier, &size, descs);
|
||||
AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(encoderSpecifier), &encoderSpecifier, &size, descs);
|
||||
for (uint32_t i = 0; i < count; i++){
|
||||
if ((type == descs[i].mSubType) && (manufacturer == descs[i].mManufacturer)){
|
||||
memcpy(&audioDesc, &descs[i], sizeof(audioDesc));
|
||||
|
||||
@@ -59,16 +59,16 @@
|
||||
}
|
||||
|
||||
_currentVideoBitRate = _configuration.videoBitRate;
|
||||
status = VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval,(__bridge CFTypeRef)@(_configuration.videoMaxKeyframeInterval));
|
||||
status = VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration,(__bridge CFTypeRef)@(_configuration.videoMaxKeyframeInterval));
|
||||
status = VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_ExpectedFrameRate, (__bridge CFTypeRef)@(_configuration.videoFrameRate));
|
||||
status = VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(_configuration.videoBitRate));
|
||||
VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval,(__bridge CFTypeRef)@(_configuration.videoMaxKeyframeInterval));
|
||||
VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration,(__bridge CFTypeRef)@(_configuration.videoMaxKeyframeInterval));
|
||||
VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_ExpectedFrameRate, (__bridge CFTypeRef)@(_configuration.videoFrameRate));
|
||||
VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(_configuration.videoBitRate));
|
||||
NSArray *limit = @[@(_configuration.videoBitRate * 1.5/8),@(1)];
|
||||
status = VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_DataRateLimits, (__bridge CFArrayRef)limit);
|
||||
status = VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_RealTime, kCFBooleanFalse);
|
||||
status = VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Main_AutoLevel);
|
||||
status = VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse);
|
||||
status = VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_H264EntropyMode, kVTH264EntropyMode_CABAC);
|
||||
VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_DataRateLimits, (__bridge CFArrayRef)limit);
|
||||
VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_RealTime, kCFBooleanFalse);
|
||||
VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Main_AutoLevel);
|
||||
VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse);
|
||||
VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_H264EntropyMode, kVTH264EntropyMode_CABAC);
|
||||
VTCompressionSessionPrepareToEncodeFrames(compressionSession);
|
||||
|
||||
}
|
||||
@@ -238,7 +238,6 @@ static void VideoCompressonOutputCallback(void *VTref, void *VTFrameRef, OSStatu
|
||||
|
||||
|
||||
- (char*)GetFilePathByfileName:(char*)filename
|
||||
|
||||
{
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);
|
||||
NSString *documentsDirectory = [paths objectAtIndex:0];
|
||||
|
||||
@@ -50,8 +50,8 @@ typedef NS_ENUM(NSUInteger, LFLiveVideoQuality){
|
||||
/// 视频配置(质量)
|
||||
+ (instancetype)defaultConfigurationForQuality:(LFLiveVideoQuality)videoQuality;
|
||||
|
||||
/// 视频配置(质量 & 方向)
|
||||
+ (instancetype)defaultConfigurationForQuality:(LFLiveVideoQuality)videoQuality orientation:(UIInterfaceOrientation)orientation;
|
||||
/// 视频配置(质量 & 是否是横屏)
|
||||
+ (instancetype)defaultConfigurationForQuality:(LFLiveVideoQuality)videoQuality landscape:(BOOL)landscape;
|
||||
|
||||
#pragma mark - Attribute
|
||||
///=============================================================================
|
||||
@@ -61,7 +61,7 @@ typedef NS_ENUM(NSUInteger, LFLiveVideoQuality){
|
||||
@property (nonatomic, assign) CGSize videoSize;
|
||||
|
||||
/// 视频输出方向
|
||||
@property (nonatomic, assign) UIInterfaceOrientation orientation;
|
||||
@property (nonatomic, assign) BOOL landscape;
|
||||
|
||||
/// 视频的帧率,即 fps
|
||||
@property (nonatomic, assign) NSUInteger videoFrameRate;
|
||||
|
||||
@@ -18,110 +18,119 @@
|
||||
}
|
||||
|
||||
+ (instancetype)defaultConfigurationForQuality:(LFLiveVideoQuality)videoQuality{
|
||||
LFLiveVideoConfiguration *configuration = [LFLiveVideoConfiguration defaultConfigurationForQuality:videoQuality orientation:UIInterfaceOrientationPortrait];
|
||||
LFLiveVideoConfiguration *configuration = [LFLiveVideoConfiguration defaultConfigurationForQuality:videoQuality landscape:NO];
|
||||
return configuration;
|
||||
}
|
||||
|
||||
+ (instancetype)defaultConfigurationForQuality:(LFLiveVideoQuality)videoQuality orientation:(UIInterfaceOrientation)orientation{
|
||||
+ (instancetype)defaultConfigurationForQuality:(LFLiveVideoQuality)videoQuality landscape:(BOOL)landscape{
|
||||
LFLiveVideoConfiguration *configuration = [LFLiveVideoConfiguration new];
|
||||
switch (videoQuality) {
|
||||
case LFLiveVideoQuality_Low1:
|
||||
case LFLiveVideoQuality_Low1:
|
||||
{
|
||||
configuration.sessionPreset = LFCaptureSessionPreset360x640;
|
||||
configuration.videoFrameRate = 15;
|
||||
configuration.videoMaxFrameRate = 15;
|
||||
configuration.videoMinFrameRate = 10;
|
||||
configuration.videoBitRate = 500 * 1024;
|
||||
configuration.videoMaxBitRate = 600 * 1024;
|
||||
configuration.videoMinBitRate = 250 * 1024;
|
||||
configuration.videoBitRate = 500 * 1000;
|
||||
configuration.videoMaxBitRate = 600 * 1000;
|
||||
configuration.videoMinBitRate = 400 * 1000;
|
||||
configuration.videoSize = CGSizeMake(360, 640);
|
||||
}
|
||||
break;
|
||||
case LFLiveVideoQuality_Low2:
|
||||
case LFLiveVideoQuality_Low2:
|
||||
{
|
||||
configuration.sessionPreset = LFCaptureSessionPreset360x640;
|
||||
configuration.videoFrameRate = 24;
|
||||
configuration.videoMaxFrameRate = 24;
|
||||
configuration.videoMinFrameRate = 12;
|
||||
configuration.videoBitRate = 800 * 1024;
|
||||
configuration.videoMaxBitRate = 900 * 1024;
|
||||
configuration.videoMinBitRate = 500 * 1024;
|
||||
configuration.videoBitRate = 600 * 1000;
|
||||
configuration.videoMaxBitRate = 720 * 1000;
|
||||
configuration.videoMinBitRate = 500 * 1000;
|
||||
configuration.videoSize = CGSizeMake(360, 640);
|
||||
}
|
||||
break;
|
||||
case LFLiveVideoQuality_Low3:
|
||||
case LFLiveVideoQuality_Low3:
|
||||
{
|
||||
configuration.sessionPreset = LFCaptureSessionPreset360x640;
|
||||
configuration.videoFrameRate = 30;
|
||||
configuration.videoMaxFrameRate = 30;
|
||||
configuration.videoMinFrameRate = 15;
|
||||
configuration.videoBitRate = 800 * 1024;
|
||||
configuration.videoMaxBitRate = 900 * 1024;
|
||||
configuration.videoMinBitRate = 500 * 1024;
|
||||
configuration.videoBitRate = 800 * 1000;
|
||||
configuration.videoMaxBitRate = 960 * 1000;
|
||||
configuration.videoMinBitRate = 600 * 1000;
|
||||
configuration.videoSize = CGSizeMake(360, 640);
|
||||
}
|
||||
break;
|
||||
case LFLiveVideoQuality_Medium1:
|
||||
case LFLiveVideoQuality_Medium1:
|
||||
{
|
||||
configuration.sessionPreset = LFCaptureSessionPreset540x960;
|
||||
configuration.videoFrameRate = 15;
|
||||
configuration.videoMaxFrameRate = 15;
|
||||
configuration.videoMinFrameRate = 10;
|
||||
configuration.videoBitRate = 800 * 1024;
|
||||
configuration.videoMaxBitRate = 900 * 1024;
|
||||
configuration.videoMinBitRate = 500 * 1024;
|
||||
configuration.videoBitRate = 800 * 1000;
|
||||
configuration.videoMaxBitRate = 960 * 1000;
|
||||
configuration.videoMinBitRate = 500 * 1000;
|
||||
configuration.videoSize = CGSizeMake(540, 960);
|
||||
}
|
||||
break;
|
||||
case LFLiveVideoQuality_Medium2:
|
||||
case LFLiveVideoQuality_Medium2:
|
||||
{
|
||||
configuration.sessionPreset = LFCaptureSessionPreset540x960;
|
||||
configuration.videoFrameRate = 24;
|
||||
configuration.videoMaxFrameRate = 24;
|
||||
configuration.videoMinFrameRate = 12;
|
||||
configuration.videoBitRate = 800 * 1024;
|
||||
configuration.videoMaxBitRate = 900 * 1024;
|
||||
configuration.videoMinBitRate = 500 * 1024;
|
||||
configuration.videoBitRate = 800 * 1000;
|
||||
configuration.videoMaxBitRate = 960 * 1000;
|
||||
configuration.videoMinBitRate = 500 * 1000;
|
||||
configuration.videoSize = CGSizeMake(540, 960);
|
||||
}
|
||||
break;
|
||||
case LFLiveVideoQuality_Medium3:
|
||||
case LFLiveVideoQuality_Medium3:
|
||||
{
|
||||
configuration.sessionPreset = LFCaptureSessionPreset540x960;
|
||||
configuration.videoFrameRate = 30;
|
||||
configuration.videoMaxFrameRate = 30;
|
||||
configuration.videoMinFrameRate = 15;
|
||||
configuration.videoBitRate = 1000 * 1024;
|
||||
configuration.videoMaxBitRate = 1200 * 1024;
|
||||
configuration.videoMinBitRate = 500 * 1024;
|
||||
configuration.videoBitRate = 1000 * 1000;
|
||||
configuration.videoMaxBitRate = 1200 * 1000;
|
||||
configuration.videoMinBitRate = 500 * 1000;
|
||||
configuration.videoSize = CGSizeMake(540, 960);
|
||||
}
|
||||
break;
|
||||
case LFLiveVideoQuality_High1:
|
||||
case LFLiveVideoQuality_High1:
|
||||
{
|
||||
configuration.sessionPreset = LFCaptureSessionPreset720x1280;
|
||||
configuration.videoFrameRate = 15;
|
||||
configuration.videoMaxFrameRate = 15;
|
||||
configuration.videoMinFrameRate = 10;
|
||||
configuration.videoBitRate = 1000 * 1024;
|
||||
configuration.videoMaxBitRate = 1200 * 1024;
|
||||
configuration.videoMinBitRate = 500 * 1024;
|
||||
configuration.videoBitRate = 1000 * 1000;
|
||||
configuration.videoMaxBitRate = 1200 * 1000;
|
||||
configuration.videoMinBitRate = 500 * 1000;
|
||||
configuration.videoSize = CGSizeMake(720, 1280);
|
||||
}
|
||||
break;
|
||||
case LFLiveVideoQuality_High2:
|
||||
case LFLiveVideoQuality_High2:
|
||||
{
|
||||
configuration.sessionPreset = LFCaptureSessionPreset720x1280;
|
||||
configuration.videoFrameRate = 24;
|
||||
configuration.videoMaxFrameRate = 24;
|
||||
configuration.videoMinFrameRate = 12;
|
||||
configuration.videoBitRate = 1200 * 1024;
|
||||
configuration.videoMaxBitRate = 1300 * 1024;
|
||||
configuration.videoMinBitRate = 800 * 1024;
|
||||
configuration.videoBitRate = 1200 * 1000;
|
||||
configuration.videoMaxBitRate = 1440 * 1000;
|
||||
configuration.videoMinBitRate = 800 * 1000;
|
||||
configuration.videoSize = CGSizeMake(720, 1280);
|
||||
}
|
||||
break;
|
||||
case LFLiveVideoQuality_High3:
|
||||
case LFLiveVideoQuality_High3:
|
||||
{
|
||||
configuration.sessionPreset = LFCaptureSessionPreset720x1280;
|
||||
configuration.videoFrameRate = 30;
|
||||
configuration.videoMaxFrameRate = 30;
|
||||
configuration.videoMinFrameRate = 15;
|
||||
configuration.videoBitRate = 1200 * 1024;
|
||||
configuration.videoMaxBitRate = 1300 * 1024;
|
||||
configuration.videoMinBitRate = 500 * 1024;
|
||||
configuration.videoBitRate = 1200 * 1000;
|
||||
configuration.videoMaxBitRate = 1440 * 1000;
|
||||
configuration.videoMinBitRate = 500 * 1000;
|
||||
configuration.videoSize = CGSizeMake(720, 1280);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -129,11 +138,12 @@
|
||||
}
|
||||
configuration.sessionPreset = [configuration supportSessionPreset:configuration.sessionPreset];
|
||||
configuration.videoMaxKeyframeInterval = configuration.videoFrameRate*2;
|
||||
configuration.orientation = orientation;
|
||||
if(orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown){
|
||||
configuration.videoSize = CGSizeMake(368, 640);
|
||||
configuration.landscape = landscape;
|
||||
CGSize size = configuration.videoSize;
|
||||
if(landscape){
|
||||
configuration.videoSize = CGSizeMake(size.height, size.width);
|
||||
}else{
|
||||
configuration.videoSize = CGSizeMake(640, 368);
|
||||
configuration.videoSize = CGSizeMake(size.width, size.height);
|
||||
}
|
||||
return configuration;
|
||||
}
|
||||
@@ -142,17 +152,17 @@
|
||||
- (NSString*)avSessionPreset{
|
||||
NSString *avSessionPreset = nil;
|
||||
switch (self.sessionPreset) {
|
||||
case LFCaptureSessionPreset360x640:
|
||||
case LFCaptureSessionPreset360x640:
|
||||
{
|
||||
avSessionPreset = AVCaptureSessionPreset640x480;
|
||||
}
|
||||
break;
|
||||
case LFCaptureSessionPreset540x960:
|
||||
case LFCaptureSessionPreset540x960:
|
||||
{
|
||||
avSessionPreset = AVCaptureSessionPresetiFrame960x540;
|
||||
}
|
||||
break;
|
||||
case LFCaptureSessionPreset720x1280:
|
||||
case LFCaptureSessionPreset720x1280:
|
||||
{
|
||||
avSessionPreset = AVCaptureSessionPreset1280x720;
|
||||
}
|
||||
@@ -185,6 +195,15 @@
|
||||
_videoMinFrameRate = videoMinFrameRate;
|
||||
}
|
||||
|
||||
- (void)setLandscape:(BOOL)landscape{
|
||||
_landscape = landscape;
|
||||
CGSize size = self.videoSize;
|
||||
if(landscape){
|
||||
self.videoSize = CGSizeMake(size.height, size.width);
|
||||
}else{
|
||||
self.videoSize = CGSizeMake(size.width, size.height);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -- Custom Method
|
||||
- (LFLiveVideoSessionPreset)supportSessionPreset:(LFLiveVideoSessionPreset)sessionPreset{
|
||||
@@ -215,7 +234,7 @@
|
||||
[aCoder encodeObject:@(self.videoMaxKeyframeInterval) forKey:@"videoMaxKeyframeInterval"];
|
||||
[aCoder encodeObject:@(self.videoBitRate) forKey:@"videoBitRate"];
|
||||
[aCoder encodeObject:@(self.sessionPreset) forKey:@"sessionPreset"];
|
||||
[aCoder encodeObject:@(self.orientation) forKey:@"orientation"];
|
||||
[aCoder encodeObject:@(self.landscape) forKey:@"landscape"];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder {
|
||||
@@ -225,7 +244,7 @@
|
||||
_videoMaxKeyframeInterval = [[aDecoder decodeObjectForKey:@"videoMaxKeyframeInterval"] unsignedIntegerValue];
|
||||
_videoBitRate = [[aDecoder decodeObjectForKey:@"videoBitRate"] unsignedIntegerValue];
|
||||
_sessionPreset = [[aDecoder decodeObjectForKey:@"sessionPreset"] unsignedIntegerValue];
|
||||
_orientation = [[aDecoder decodeObjectForKey:@"orientation"] unsignedIntegerValue];
|
||||
_landscape = [[aDecoder decodeObjectForKey:@"landscape"] unsignedIntegerValue];
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -242,7 +261,7 @@
|
||||
@(self.isClipVideo),
|
||||
self.avSessionPreset,
|
||||
@(self.sessionPreset),
|
||||
@(self.orientation),];
|
||||
@(self.landscape),];
|
||||
|
||||
for (NSObject *value in values) {
|
||||
hash ^= value.hash;
|
||||
@@ -269,7 +288,7 @@
|
||||
object.isClipVideo == self.isClipVideo &&
|
||||
[object.avSessionPreset isEqualToString:self.avSessionPreset] &&
|
||||
object.sessionPreset == self.sessionPreset &&
|
||||
object.orientation == self.orientation;
|
||||
object.landscape == self.landscape;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,7 +311,7 @@
|
||||
[desc appendFormat:@" isClipVideo:%zi",self.isClipVideo];
|
||||
[desc appendFormat:@" avSessionPreset:%@",self.avSessionPreset];
|
||||
[desc appendFormat:@" sessionPreset:%zi",self.sessionPreset];
|
||||
[desc appendFormat:@" orientation:%zi",self.orientation];
|
||||
[desc appendFormat:@" landscape:%zi",self.landscape];
|
||||
return desc;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,47 +1,9 @@
|
||||
#import "GPUImageFilterGroup.h"
|
||||
#import "GPUImageFilter.h"
|
||||
|
||||
@class GPUImageGaussianBlurFilter;
|
||||
//@class GPUImageBilateralFilter;
|
||||
|
||||
/** A Gaussian blur that preserves focus within a circular region
|
||||
*/
|
||||
@interface LFGPUImageBeautyFilter : GPUImageFilterGroup
|
||||
{
|
||||
GPUImageGaussianBlurFilter *blurFilter;
|
||||
GPUImageFilter *selectiveFocusFilter;
|
||||
BOOL hasOverriddenAspectRatio;
|
||||
@interface LFGPUImageBeautyFilter : GPUImageFilter {
|
||||
}
|
||||
|
||||
/** The radius of the circular area being excluded from the blur
|
||||
*/
|
||||
@property (readwrite, nonatomic) CGFloat excludeCircleRadius;
|
||||
/** The center of the circular area being excluded from the blur
|
||||
*/
|
||||
@property (readwrite, nonatomic) CGPoint excludeCirclePoint;
|
||||
/** The size of the area between the blurred portion and the clear circle
|
||||
*/
|
||||
@property (readwrite, nonatomic) CGFloat excludeBlurSize;
|
||||
/** A radius in pixels to use for the blur, with a default of 5.0. This adjusts the sigma variable in the Gaussian distribution function.
|
||||
*/
|
||||
@property (readwrite, nonatomic) CGFloat blurRadiusInPixels;
|
||||
/** The aspect ratio of the image, used to adjust the circularity of the in-focus region. By default, this matches the image aspect ratio, but you can override this value.
|
||||
*/
|
||||
@property (readwrite, nonatomic) CGFloat aspectRatio;
|
||||
|
||||
@property (nonatomic, assign) CGFloat beautyLevel;
|
||||
@property (nonatomic, assign) CGFloat brightLevel;
|
||||
@property (nonatomic, assign) CGFloat toneLevel;
|
||||
@end
|
||||
|
||||
|
||||
|
||||
//#import <GPUImage/GPUImage.h>
|
||||
//
|
||||
//@class GPUImageCombinationFilter;
|
||||
//
|
||||
//@interface LFGPUImageBeautyFilter : GPUImageFilterGroup {
|
||||
// //GPUImageBilateralFilter *bilateralFilter;
|
||||
// GPUImageGaussianBlurFilter *bilateralFilter;
|
||||
// GPUImageCannyEdgeDetectionFilter *cannyEdgeFilter;
|
||||
// GPUImageCombinationFilter *combinationFilter;
|
||||
// GPUImageHSBFilter *hsbFilter;
|
||||
//}
|
||||
//
|
||||
//@end
|
||||
@@ -1,478 +1,276 @@
|
||||
#import "LFGPUImageBeautyFilter.h"
|
||||
#import "GPUImageGaussianBlurFilter.h"
|
||||
#import "GPUImageTwoInputFilter.h"
|
||||
#import "GPUImageBilateralFilter.h"
|
||||
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
|
||||
NSString *const KLFGPUImageBeautyFragmentShaderString = SHADER_STRING
|
||||
(
|
||||
NSString *const kLFGPUImageBeautyFragmentShaderString = SHADER_STRING
|
||||
(
|
||||
varying highp vec2 textureCoordinate;
|
||||
varying highp vec2 textureCoordinate2;
|
||||
|
||||
uniform sampler2D inputImageTexture;
|
||||
uniform sampler2D inputImageTexture2;
|
||||
|
||||
uniform lowp float excludeCircleRadius;
|
||||
uniform lowp vec2 excludeCirclePoint;
|
||||
uniform lowp float excludeBlurSize;
|
||||
uniform highp float aspectRatio;
|
||||
//247 217 211
|
||||
|
||||
uniform highp vec2 singleStepOffset;
|
||||
uniform highp vec4 params;
|
||||
uniform highp float brightness;
|
||||
|
||||
const highp vec3 W = vec3(0.299,0.587,0.114);
|
||||
const highp mat3 saturateMatrix = mat3(
|
||||
1.1102,-0.0598,-0.061,
|
||||
-0.0774,1.0826,-0.1186,
|
||||
-0.0228,-0.0228,1.1772);
|
||||
highp vec2 blurCoordinates[24];
|
||||
|
||||
highp float hardLight(highp float color) {
|
||||
if(color <= 0.5)
|
||||
color = color * color * 2.0;
|
||||
else
|
||||
color = 1.0 - ((1.0 - color)*(1.0 - color) * 2.0);
|
||||
return color;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
lowp vec4 tmpColor = texture2D(inputImageTexture, textureCoordinate);
|
||||
// gl_FragColor = tmpColor;
|
||||
// return;
|
||||
lowp vec4 sharpImageColor = texture2D(inputImageTexture, textureCoordinate);
|
||||
lowp vec4 blurredImageColor = texture2D(inputImageTexture2, textureCoordinate2);
|
||||
if((sharpImageColor.r > 0.372549 && sharpImageColor.g > 0.156863 && sharpImageColor.b > 0.078431 &&
|
||||
sharpImageColor.r - sharpImageColor.g > 0.058823 && sharpImageColor.r - sharpImageColor.b > 0.058823) ||
|
||||
(sharpImageColor.r > 0.784314 && sharpImageColor.g > 0.823530 && sharpImageColor.b > 0.666667 &&
|
||||
abs(sharpImageColor.r - sharpImageColor.b) <= 0.058823 && sharpImageColor.r > sharpImageColor.b && sharpImageColor.g > sharpImageColor.b)) {
|
||||
|
||||
mediump float rpass;
|
||||
mediump float gpass;
|
||||
mediump float bpass;
|
||||
mediump float hpass;
|
||||
hpass = 0.5 / excludeBlurSize;
|
||||
hpass = 1.0;
|
||||
|
||||
mediump float dis = distance(sharpImageColor, blurredImageColor);
|
||||
bpass = min((0.4+1.0*(1.0*dis)), 1.0);
|
||||
gl_FragColor = mix(sharpImageColor, blurredImageColor, 1.0-bpass);
|
||||
|
||||
} else {
|
||||
mediump float rpass;
|
||||
mediump float gpass;
|
||||
mediump float bpass;
|
||||
mediump float hpass;
|
||||
hpass = 0.5 / excludeBlurSize;
|
||||
hpass = 1.0;
|
||||
mediump float dis = distance(sharpImageColor, blurredImageColor);
|
||||
bpass = min((0.45+1.0*(1.0*dis)), 1.0);
|
||||
gl_FragColor = mix(sharpImageColor, blurredImageColor, 1.0-bpass);
|
||||
|
||||
highp vec3 centralColor = texture2D(inputImageTexture, textureCoordinate).rgb;
|
||||
blurCoordinates[0] = textureCoordinate.xy + singleStepOffset * vec2(0.0, -10.0);
|
||||
blurCoordinates[1] = textureCoordinate.xy + singleStepOffset * vec2(0.0, 10.0);
|
||||
blurCoordinates[2] = textureCoordinate.xy + singleStepOffset * vec2(-10.0, 0.0);
|
||||
blurCoordinates[3] = textureCoordinate.xy + singleStepOffset * vec2(10.0, 0.0);
|
||||
blurCoordinates[4] = textureCoordinate.xy + singleStepOffset * vec2(5.0, -8.0);
|
||||
blurCoordinates[5] = textureCoordinate.xy + singleStepOffset * vec2(5.0, 8.0);
|
||||
blurCoordinates[6] = textureCoordinate.xy + singleStepOffset * vec2(-5.0, 8.0);
|
||||
blurCoordinates[7] = textureCoordinate.xy + singleStepOffset * vec2(-5.0, -8.0);
|
||||
blurCoordinates[8] = textureCoordinate.xy + singleStepOffset * vec2(8.0, -5.0);
|
||||
blurCoordinates[9] = textureCoordinate.xy + singleStepOffset * vec2(8.0, 5.0);
|
||||
blurCoordinates[10] = textureCoordinate.xy + singleStepOffset * vec2(-8.0, 5.0);
|
||||
blurCoordinates[11] = textureCoordinate.xy + singleStepOffset * vec2(-8.0, -5.0);
|
||||
blurCoordinates[12] = textureCoordinate.xy + singleStepOffset * vec2(0.0, -6.0);
|
||||
blurCoordinates[13] = textureCoordinate.xy + singleStepOffset * vec2(0.0, 6.0);
|
||||
blurCoordinates[14] = textureCoordinate.xy + singleStepOffset * vec2(6.0, 0.0);
|
||||
blurCoordinates[15] = textureCoordinate.xy + singleStepOffset * vec2(-6.0, 0.0);
|
||||
blurCoordinates[16] = textureCoordinate.xy + singleStepOffset * vec2(-4.0, -4.0);
|
||||
blurCoordinates[17] = textureCoordinate.xy + singleStepOffset * vec2(-4.0, 4.0);
|
||||
blurCoordinates[18] = textureCoordinate.xy + singleStepOffset * vec2(4.0, -4.0);
|
||||
blurCoordinates[19] = textureCoordinate.xy + singleStepOffset * vec2(4.0, 4.0);
|
||||
blurCoordinates[20] = textureCoordinate.xy + singleStepOffset * vec2(-2.0, -2.0);
|
||||
blurCoordinates[21] = textureCoordinate.xy + singleStepOffset * vec2(-2.0, 2.0);
|
||||
blurCoordinates[22] = textureCoordinate.xy + singleStepOffset * vec2(2.0, -2.0);
|
||||
blurCoordinates[23] = textureCoordinate.xy + singleStepOffset * vec2(2.0, 2.0);
|
||||
|
||||
highp float sampleColor = centralColor.g * 22.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[0]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[1]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[2]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[3]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[4]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[5]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[6]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[7]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[8]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[9]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[10]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[11]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[12]).g * 2.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[13]).g * 2.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[14]).g * 2.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[15]).g * 2.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[16]).g * 2.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[17]).g * 2.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[18]).g * 2.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[19]).g * 2.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[20]).g * 3.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[21]).g * 3.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[22]).g * 3.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[23]).g * 3.0;
|
||||
|
||||
sampleColor = sampleColor / 62.0;
|
||||
|
||||
highp float highPass = centralColor.g - sampleColor + 0.5;
|
||||
|
||||
for(int i = 0; i < 5;i++)
|
||||
{
|
||||
highPass = hardLight(highPass);
|
||||
}
|
||||
|
||||
mediump float r;
|
||||
mediump float g;
|
||||
mediump float b;
|
||||
mediump float status = 0.81;
|
||||
// r = min((gl_FragColor.r+sharpImageColor.r - sharpImageColor.r*gl_FragColor.r), 1.0);
|
||||
// g = min((gl_FragColor.g+sharpImageColor.g - sharpImageColor.g*gl_FragColor.g), 1.0);
|
||||
// b = min((gl_FragColor.b+sharpImageColor.b - sharpImageColor.b*gl_FragColor.b), 1.0);
|
||||
r = min((gl_FragColor.r*2.0 - gl_FragColor.r*gl_FragColor.r), 1.0);
|
||||
g = min((gl_FragColor.g*2.0 - gl_FragColor.g*gl_FragColor.g), 1.0);
|
||||
b = min((gl_FragColor.b*2.0 - gl_FragColor.b*gl_FragColor.b), 1.0);
|
||||
r = min(status*gl_FragColor.r+1.05*(1.0-status)*r, 1.0);
|
||||
g = min(status*gl_FragColor.g+1.15*(1.0-status)*g, 1.0);
|
||||
b = min(status*gl_FragColor.b+1.25*(1.0-status)*b, 1.0);
|
||||
gl_FragColor = vec4(r, g, b, 1.0);
|
||||
//gl_FragColor = vec4(r, g, b, 1.0);
|
||||
|
||||
highp float lumance = dot(centralColor, W);
|
||||
|
||||
highp float alpha = pow(lumance, params.r);
|
||||
|
||||
highp vec3 smoothColor = centralColor + (centralColor-vec3(highPass))*alpha*0.1;
|
||||
|
||||
smoothColor.r = clamp(pow(smoothColor.r, params.g),0.0,1.0);
|
||||
smoothColor.g = clamp(pow(smoothColor.g, params.g),0.0,1.0);
|
||||
smoothColor.b = clamp(pow(smoothColor.b, params.g),0.0,1.0);
|
||||
|
||||
highp vec3 lvse = vec3(1.0)-(vec3(1.0)-smoothColor)*(vec3(1.0)-centralColor);
|
||||
highp vec3 bianliang = max(smoothColor, centralColor);
|
||||
highp vec3 rouguang = 2.0*centralColor*smoothColor + centralColor*centralColor - 2.0*centralColor*centralColor*smoothColor;
|
||||
|
||||
gl_FragColor = vec4(mix(centralColor, lvse, alpha), 1.0);
|
||||
gl_FragColor.rgb = mix(gl_FragColor.rgb, bianliang, alpha);
|
||||
gl_FragColor.rgb = mix(gl_FragColor.rgb, rouguang, params.b);
|
||||
|
||||
highp vec3 satcolor = gl_FragColor.rgb * saturateMatrix;
|
||||
gl_FragColor.rgb = mix(gl_FragColor.rgb, satcolor, params.a);
|
||||
gl_FragColor.rgb = vec3(gl_FragColor.rgb + vec3(brightness));
|
||||
}
|
||||
);
|
||||
);
|
||||
#else
|
||||
NSString *const KLFGPUImageBeautyFragmentShaderString = SHADER_STRING
|
||||
NSString *const kLFGPUImageBeautyFragmentShaderString = SHADER_STRING
|
||||
(
|
||||
varying vec2 textureCoordinate;
|
||||
varying vec2 textureCoordinate2;
|
||||
|
||||
uniform sampler2D inputImageTexture;
|
||||
uniform sampler2D inputImageTexture2;
|
||||
|
||||
uniform float excludeCircleRadius;
|
||||
uniform vec2 excludeCirclePoint;
|
||||
uniform float excludeBlurSize;
|
||||
uniform float aspectRatio;
|
||||
uniform mediump vec2 singleStepOffset;
|
||||
uniform mediump vec4 params;
|
||||
uniform mediump float brightness;
|
||||
const mediump mat3 saturateMatrix = mat3(
|
||||
1.1102,-0.0598,-0.061,
|
||||
-0.0774,1.0826,-0.1186,
|
||||
-0.0228,-0.0228,1.1772);
|
||||
const mediump vec3 W = vec3(0.299,0.587,0.114);
|
||||
mediump vec2 blurCoordinates[24];
|
||||
|
||||
mediump float hardLight(mediump float color)
|
||||
{
|
||||
if(color <= 0.5)
|
||||
color = color * color * 2.0;
|
||||
else
|
||||
color = 1.0 - ((1.0 - color)*(1.0 - color) * 2.0);
|
||||
return color;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
// vec4 sharpImageColor = texture2D(inputImageTexture, textureCoordinate);
|
||||
// vec4 blurredImageColor = texture2D(inputImageTexture2, textureCoordinate2);
|
||||
//
|
||||
// vec2 textureCoordinateToUse = vec2(textureCoordinate2.x, (textureCoordinate2.y * aspectRatio + 0.5 - 0.5 * aspectRatio));
|
||||
// float distanceFromCenter = distance(excludeCirclePoint, textureCoordinateToUse);
|
||||
//
|
||||
// gl_FragColor = mix(sharpImageColor, blurredImageColor, smoothstep(excludeCircleRadius - excludeBlurSize, excludeCircleRadius, distanceFromCenter));
|
||||
mediump vec3 centralColor = texture2D(inputImageTexture, textureCoordinate).rgb;
|
||||
blurCoordinates[0] = textureCoordinate.xy + singleStepOffset * vec2(0.0, -10.0);
|
||||
blurCoordinates[1] = textureCoordinate.xy + singleStepOffset * vec2(0.0, 10.0);
|
||||
blurCoordinates[2] = textureCoordinate.xy + singleStepOffset * vec2(-10.0, 0.0);
|
||||
blurCoordinates[3] = textureCoordinate.xy + singleStepOffset * vec2(10.0, 0.0);
|
||||
blurCoordinates[4] = textureCoordinate.xy + singleStepOffset * vec2(5.0, -8.0);
|
||||
blurCoordinates[5] = textureCoordinate.xy + singleStepOffset * vec2(5.0, 8.0);
|
||||
blurCoordinates[6] = textureCoordinate.xy + singleStepOffset * vec2(-5.0, 8.0);
|
||||
blurCoordinates[7] = textureCoordinate.xy + singleStepOffset * vec2(-5.0, -8.0);
|
||||
blurCoordinates[8] = textureCoordinate.xy + singleStepOffset * vec2(8.0, -5.0);
|
||||
blurCoordinates[9] = textureCoordinate.xy + singleStepOffset * vec2(8.0, 5.0);
|
||||
blurCoordinates[10] = textureCoordinate.xy + singleStepOffset * vec2(-8.0, 5.0);
|
||||
blurCoordinates[11] = textureCoordinate.xy + singleStepOffset * vec2(-8.0, -5.0);
|
||||
blurCoordinates[12] = textureCoordinate.xy + singleStepOffset * vec2(0.0, -6.0);
|
||||
blurCoordinates[13] = textureCoordinate.xy + singleStepOffset * vec2(0.0, 6.0);
|
||||
blurCoordinates[14] = textureCoordinate.xy + singleStepOffset * vec2(6.0, 0.0);
|
||||
blurCoordinates[15] = textureCoordinate.xy + singleStepOffset * vec2(-6.0, 0.0);
|
||||
blurCoordinates[16] = textureCoordinate.xy + singleStepOffset * vec2(-4.0, -4.0);
|
||||
blurCoordinates[17] = textureCoordinate.xy + singleStepOffset * vec2(-4.0, 4.0);
|
||||
blurCoordinates[18] = textureCoordinate.xy + singleStepOffset * vec2(4.0, -4.0);
|
||||
blurCoordinates[19] = textureCoordinate.xy + singleStepOffset * vec2(4.0, 4.0);
|
||||
blurCoordinates[20] = textureCoordinate.xy + singleStepOffset * vec2(-2.0, -2.0);
|
||||
blurCoordinates[21] = textureCoordinate.xy + singleStepOffset * vec2(-2.0, 2.0);
|
||||
blurCoordinates[22] = textureCoordinate.xy + singleStepOffset * vec2(2.0, -2.0);
|
||||
blurCoordinates[23] = textureCoordinate.xy + singleStepOffset * vec2(2.0, 2.0);
|
||||
|
||||
vec4 sharpImageColor = texture2D(inputImageTexture, textureCoordinate);
|
||||
vec4 blurredImageColor = texture2D(inputImageTexture2, textureCoordinate2);
|
||||
if(sharpImageColor.r > 0.372549/*0.372549*/) {
|
||||
mediump float bpass;
|
||||
bpass = min((sharpImageColor.r - blurredImageColor.r)*(sharpImageColor.r - blurredImageColor.r)*0.045455, 1.0);
|
||||
//bpass = min((sharpImageColor.r - blurredImageColor.r)*(sharpImageColor.r - blurredImageColor.r)*0.038462, 1.0);
|
||||
bpass = min((0.5+255.0*bpass), 1.0);
|
||||
gl_FragColor = mix(sharpImageColor, blurredImageColor, 1.0-bpass);
|
||||
} else {
|
||||
gl_FragColor = vec4(sharpImageColor.r, sharpImageColor.g, sharpImageColor.b,1.0);
|
||||
mediump float sampleColor = centralColor.g * 22.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[0]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[1]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[2]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[3]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[4]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[5]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[6]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[7]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[8]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[9]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[10]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[11]).g;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[12]).g * 2.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[13]).g * 2.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[14]).g * 2.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[15]).g * 2.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[16]).g * 2.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[17]).g * 2.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[18]).g * 2.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[19]).g * 2.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[20]).g * 3.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[21]).g * 3.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[22]).g * 3.0;
|
||||
sampleColor += texture2D(inputImageTexture, blurCoordinates[23]).g * 3.0;
|
||||
|
||||
sampleColor = sampleColor / 62.0;
|
||||
|
||||
mediump float highPass = centralColor.g - sampleColor + 0.5;
|
||||
|
||||
for(int i = 0; i < 5;i++)
|
||||
{
|
||||
highPass = hardLight(highPass);
|
||||
}
|
||||
mediump float r;
|
||||
mediump float g;
|
||||
mediump float b;
|
||||
r = min((gl_FragColor.r*2.0 - gl_FragColor.r*gl_FragColor.r), 1.0);
|
||||
g = min((gl_FragColor.g*2.0 - gl_FragColor.g*gl_FragColor.g), 1.0);
|
||||
b = min((gl_FragColor.b*2.0 - gl_FragColor.b*gl_FragColor.b), 1.0);
|
||||
mediump float luminance = dot(centralColor, W);
|
||||
|
||||
r = min(0.62*gl_FragColor.r+0.38*r, 1.0);
|
||||
g = min(0.62*gl_FragColor.g+0.38*g, 1.0);
|
||||
b = min(0.62*gl_FragColor.b+0.38*b, 1.0);
|
||||
mediump float alpha = pow(luminance, params);
|
||||
|
||||
gl_FragColor = vec4(r, g, b, 1.0);
|
||||
mediump vec3 smoothColor = centralColor + (centralColor-vec3(highPass))*alpha*0.1;
|
||||
|
||||
smoothColor.r = clamp(pow(smoothColor.r, params.g),0.0,1.0);
|
||||
smoothColor.g = clamp(pow(smoothColor.g, params.g),0.0,1.0);
|
||||
smoothColor.b = clamp(pow(smoothColor.b, params.g),0.0,1.0);
|
||||
|
||||
mediump vec3 lvse = vec3(1.0)-(vec3(1.0)-smoothColor)*(vec3(1.0)-centralColor);
|
||||
mediump vec3 bianliang = max(smoothColor, centralColor);
|
||||
mediump vec3 rouguang = 2.0*centralColor*smoothColor + centralColor*centralColor - 2.0*centralColor*centralColor*smoothColor;
|
||||
|
||||
gl_FragColor = vec4(mix(centralColor, lvse, alpha), 1.0);
|
||||
gl_FragColor.rgb = mix(gl_FragColor.rgb, bianliang, alpha);
|
||||
gl_FragColor.rgb = mix(gl_FragColor.rgb, rouguang, params.b);
|
||||
|
||||
mediump vec3 satcolor = gl_FragColor.rgb * saturateMatrix;
|
||||
gl_FragColor.rgb = mix(gl_FragColor.rgb, satcolor, params.a);
|
||||
gl_FragColor.rgb = vec3(gl_FragColor.rgb + vec3(brightness));
|
||||
}
|
||||
);
|
||||
#endif
|
||||
|
||||
@implementation LFGPUImageBeautyFilter
|
||||
|
||||
@synthesize excludeCirclePoint = _excludeCirclePoint, excludeCircleRadius = _excludeCircleRadius, excludeBlurSize = _excludeBlurSize;
|
||||
@synthesize blurRadiusInPixels = _blurRadiusInPixels;
|
||||
@synthesize aspectRatio = _aspectRatio;
|
||||
|
||||
- (id)init;
|
||||
{
|
||||
if (!(self = [super init]))
|
||||
if (!(self = [super initWithFragmentShaderFromString:kLFGPUImageBeautyFragmentShaderString]))
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
hasOverriddenAspectRatio = NO;
|
||||
|
||||
// First pass: apply a variable Gaussian blur
|
||||
blurFilter = [[GPUImageGaussianBlurFilter alloc] init];
|
||||
//blurFilter = [[GPUImageBilateralFilter alloc] init];
|
||||
blurFilter.texelSpacingMultiplier = 0.55;
|
||||
blurFilter.blurRadiusInPixels = 23.0;
|
||||
[self addFilter:blurFilter];
|
||||
|
||||
// Second pass: combine the blurred image with the original sharp one
|
||||
selectiveFocusFilter = [[GPUImageTwoInputFilter alloc] initWithFragmentShaderFromString:KLFGPUImageBeautyFragmentShaderString];
|
||||
[self addFilter:selectiveFocusFilter];
|
||||
|
||||
// Texture location 0 needs to be the sharp image for both the blur and the second stage processing
|
||||
[blurFilter addTarget:selectiveFocusFilter atTextureLocation:1];
|
||||
|
||||
// To prevent double updating of this filter, disable updates from the sharp image side
|
||||
self.initialFilters = [NSArray arrayWithObjects:blurFilter, selectiveFocusFilter, nil];
|
||||
self.terminalFilter = selectiveFocusFilter;
|
||||
|
||||
//self.blurRadiusInPixels = 5.0;
|
||||
//960*540
|
||||
self.blurRadiusInPixels = 13.0;
|
||||
//1280*720
|
||||
//self.blurRadiusInPixels = 11.0;
|
||||
|
||||
self.excludeCircleRadius = 13.0;
|
||||
self.excludeCirclePoint = CGPointMake(0.5f, 0.5f);
|
||||
self.excludeBlurSize = 13.0;
|
||||
|
||||
|
||||
|
||||
|
||||
_toneLevel = 0.5;
|
||||
_beautyLevel = 0.5;
|
||||
_brightLevel = 0.5;
|
||||
[self setParams:_beautyLevel tone:_toneLevel];
|
||||
[self setBrightLevel:_brightLevel];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex;
|
||||
- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex
|
||||
{
|
||||
CGSize oldInputSize = inputTextureSize;
|
||||
[super setInputSize:newSize atIndex:textureIndex];
|
||||
inputTextureSize = newSize;
|
||||
|
||||
if ( (!CGSizeEqualToSize(oldInputSize, inputTextureSize)) && (!hasOverriddenAspectRatio) && (!CGSizeEqualToSize(newSize, CGSizeZero)) )
|
||||
{
|
||||
_aspectRatio = (inputTextureSize.width / inputTextureSize.height);
|
||||
[selectiveFocusFilter setFloat:_aspectRatio forUniformName:@"aspectRatio"];
|
||||
CGPoint offset = CGPointMake(2.0f / inputTextureSize.width, 2.0 / inputTextureSize.height);
|
||||
[self setPoint:offset forUniformName:@"singleStepOffset"];
|
||||
}
|
||||
|
||||
- (void)setBeautyLevel:(CGFloat)beautyLevel
|
||||
{
|
||||
_beautyLevel = beautyLevel;
|
||||
[self setParams:_beautyLevel tone:_toneLevel];
|
||||
}
|
||||
|
||||
- (void)setBrightLevel:(CGFloat)brightLevel
|
||||
{
|
||||
_brightLevel = brightLevel;
|
||||
[self setFloat:0.6 * (-0.5 + brightLevel) forUniformName:@"brightness"];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Accessors
|
||||
|
||||
- (void)setBlurRadiusInPixels:(CGFloat)newValue;
|
||||
{
|
||||
//newValue = 25;
|
||||
blurFilter.blurRadiusInPixels = newValue;
|
||||
_excludeCircleRadius = newValue;
|
||||
[selectiveFocusFilter setFloat:newValue forUniformName:@"excludeCircleRadius"];
|
||||
_excludeBlurSize = newValue;
|
||||
[selectiveFocusFilter setFloat:newValue forUniformName:@"excludeBlurSize"];
|
||||
}
|
||||
|
||||
- (CGFloat)blurRadiusInPixels;
|
||||
{
|
||||
return blurFilter.blurRadiusInPixels;
|
||||
}
|
||||
|
||||
- (void)setExcludeCirclePoint:(CGPoint)newValue;
|
||||
{
|
||||
_excludeCirclePoint = newValue;
|
||||
[selectiveFocusFilter setPoint:newValue forUniformName:@"excludeCirclePoint"];
|
||||
}
|
||||
|
||||
- (void)setExcludeCircleRadius:(CGFloat)newValue;
|
||||
{
|
||||
_excludeCircleRadius = newValue;
|
||||
[selectiveFocusFilter setFloat:newValue forUniformName:@"excludeCircleRadius"];
|
||||
}
|
||||
|
||||
- (void)setExcludeBlurSize:(CGFloat)newValue;
|
||||
{
|
||||
_excludeBlurSize = newValue;
|
||||
[selectiveFocusFilter setFloat:newValue forUniformName:@"excludeBlurSize"];
|
||||
}
|
||||
|
||||
- (void)setAspectRatio:(CGFloat)newValue;
|
||||
{
|
||||
hasOverriddenAspectRatio = YES;
|
||||
_aspectRatio = newValue;
|
||||
[selectiveFocusFilter setFloat:_aspectRatio forUniformName:@"aspectRatio"];
|
||||
- (void)setParams:(CGFloat)beauty tone:(CGFloat)tone {
|
||||
GPUVector4 fBeautyParam;
|
||||
fBeautyParam.one = 1.0 - 0.6 * beauty;
|
||||
fBeautyParam.two = 1.0 - 0.3 * beauty;
|
||||
fBeautyParam.three = 0.1 + 0.3 * tone;
|
||||
fBeautyParam.four = 0.1 + 0.3 * tone;
|
||||
[self setFloatVec4:fBeautyParam forUniform:@"params"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//#import "LFGPUImageBeautyFilter.h"
|
||||
//
|
||||
//// Internal CombinationFilter(It should not be used outside)
|
||||
//@interface GPUImageCombinationFilter : GPUImageThreeInputFilter
|
||||
//{
|
||||
// GLint smoothDegreeUniform;
|
||||
//}
|
||||
//
|
||||
//@property (nonatomic, assign) CGFloat intensity;
|
||||
//
|
||||
//@end
|
||||
//
|
||||
//NSString *const kGPUImageBeautifyFragmentShaderString = SHADER_STRING
|
||||
//(
|
||||
// varying highp vec2 textureCoordinate;
|
||||
// varying highp vec2 textureCoordinate2;
|
||||
// varying highp vec2 textureCoordinate3;
|
||||
//
|
||||
// uniform sampler2D inputImageTexture;
|
||||
// uniform sampler2D inputImageTexture2;
|
||||
// uniform sampler2D inputImageTexture3;
|
||||
// uniform mediump float smoothDegree;
|
||||
//
|
||||
// void main()
|
||||
// {
|
||||
//// highp vec4 bilateral = texture2D(inputImageTexture, textureCoordinate);
|
||||
//// highp vec4 canny = texture2D(inputImageTexture2, textureCoordinate2);
|
||||
//// highp vec4 origin = texture2D(inputImageTexture3,textureCoordinate3);
|
||||
//// highp vec4 smooth;
|
||||
//// lowp float r = origin.r;
|
||||
//// lowp float g = origin.g;
|
||||
//// lowp float b = origin.b;
|
||||
//// if (canny.r < 0.2 && r > 0.3725 && g > 0.1568 && b > 0.0784 && r > b && (max(max(r, g), b) - min(min(r, g), b)) > 0.0588 && abs(r-g) > 0.0588) {
|
||||
//// smooth = (1.0 - smoothDegree) * (origin - bilateral) + bilateral;
|
||||
//// }
|
||||
//// else {
|
||||
//// smooth = origin;
|
||||
//// }
|
||||
//// smooth.r = log(1.0 + 0.2 * smooth.r)/log(1.2);
|
||||
//// smooth.g = log(1.0 + 0.2 * smooth.g)/log(1.2);
|
||||
//// smooth.b = log(1.0 + 0.2 * smooth.b)/log(1.2);
|
||||
//// gl_FragColor = smooth;
|
||||
//
|
||||
//
|
||||
// highp vec4 blurredImageColor = texture2D(inputImageTexture, textureCoordinate);
|
||||
// highp vec4 canny = texture2D(inputImageTexture2, textureCoordinate2);
|
||||
// highp vec4 sharpImageColor = texture2D(inputImageTexture3,textureCoordinate3);
|
||||
//
|
||||
// //gl_FragColor = canny;
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// if((sharpImageColor.r > 0.372549 && sharpImageColor.g > 0.156863 && sharpImageColor.b > 0.078431 &&
|
||||
// sharpImageColor.r - sharpImageColor.g > 0.058823 && sharpImageColor.r - sharpImageColor.b > 0.058823) ||
|
||||
// (sharpImageColor.r > 0.784314 && sharpImageColor.g > 0.823530 && sharpImageColor.b > 0.666667 &&
|
||||
// abs(sharpImageColor.r - sharpImageColor.b) <= 0.058823 && sharpImageColor.r > sharpImageColor.b && sharpImageColor.g > sharpImageColor.b)) {
|
||||
// mediump float rpass;
|
||||
// mediump float gpass;
|
||||
// mediump float bpass;
|
||||
// mediump float hpass;
|
||||
// //hpass = 0.5 / excludeBlurSize;
|
||||
// hpass = 1.0;
|
||||
// //1280*720
|
||||
// //bpass = min((sharpImageColor.r - blurredImageColor.r)*(sharpImageColor.r - blurredImageColor.r)*0.045455, 1.0);
|
||||
// //960*540
|
||||
// rpass = min(((sharpImageColor.r - blurredImageColor.r)*hpass), 1.0);
|
||||
// gpass = min(((sharpImageColor.g - blurredImageColor.g)*hpass), 1.0);
|
||||
// bpass = min(((sharpImageColor.b - blurredImageColor.b)*hpass), 1.0);
|
||||
// bpass = min((0.0+1.0*(1.0*bpass)), 1.0);
|
||||
// gpass = min((0.0+1.0*(1.0*gpass)), 1.0);
|
||||
// rpass = min((0.0+1.0*(1.0*rpass)), 1.0);
|
||||
// mediump float dis = distance(sharpImageColor, blurredImageColor);
|
||||
// bpass = min((0.35+1.0*(1.0*dis)), 1.0);
|
||||
// if(canny.r > 0.2) {
|
||||
// bpass = min((0.45+1.0*(1.0*dis)), 1.0);
|
||||
// }
|
||||
// gl_FragColor = mix(sharpImageColor, blurredImageColor, 1.0-bpass);
|
||||
//
|
||||
//
|
||||
// } else {
|
||||
// mediump float rpass;
|
||||
// mediump float gpass;
|
||||
// mediump float bpass;
|
||||
// mediump float hpass;
|
||||
// //hpass = 0.5 / excludeBlurSize;
|
||||
// hpass = 1.0;
|
||||
// //1280*720
|
||||
// //bpass = min((sharpImageColor.r - blurredImageColor.r)*(sharpImageColor.r - blurredImageColor.r)*0.045455, 1.0);
|
||||
// //960*540
|
||||
// rpass = min(((sharpImageColor.r - blurredImageColor.r)*hpass), 1.0);
|
||||
// gpass = min(((sharpImageColor.g - blurredImageColor.g)*hpass), 1.0);
|
||||
// bpass = min(((sharpImageColor.b - blurredImageColor.b)*hpass), 1.0);
|
||||
// bpass = min((0.5+1.0*(1.0*bpass)), 1.0);
|
||||
// gpass = min((0.5+1.0*(1.0*gpass)), 1.0);
|
||||
// rpass = min((0.5+1.0*(1.0*rpass)), 1.0);
|
||||
//
|
||||
// // gpass = max(rpass, gpass);
|
||||
// // bpass = max(gpass, bpass);
|
||||
// // bpass = min((0.5+1.0*(1.0*bpass)), 1.0);
|
||||
// //gl_FragColor = vec4(1, 1, 1, 1.0);
|
||||
// mediump float dis = distance(sharpImageColor, blurredImageColor);
|
||||
// bpass = min((0.45+1.0*(1.0*dis)), 1.0);
|
||||
// if(canny.r > 0.2) {
|
||||
// bpass = min((0.45+1.0*(1.0*dis)), 1.0);
|
||||
// }
|
||||
// gl_FragColor = mix(sharpImageColor, blurredImageColor, 1.0-bpass);
|
||||
////
|
||||
// }
|
||||
//
|
||||
//
|
||||
// //gl_FragColor = canny;
|
||||
//
|
||||
//
|
||||
// mediump float r;
|
||||
// mediump float g;
|
||||
// mediump float b;
|
||||
// mediump float status = 0.8;
|
||||
// r = min((gl_FragColor.r*2.0 - gl_FragColor.r*gl_FragColor.r), 1.0);
|
||||
// g = min((gl_FragColor.g*2.0 - gl_FragColor.g*gl_FragColor.g), 1.0);
|
||||
// b = min((gl_FragColor.b*2.0 - gl_FragColor.b*gl_FragColor.b), 1.0);
|
||||
// r = min(status*gl_FragColor.r+(1.0-status)*r, 1.0);
|
||||
// g = min(status*gl_FragColor.g+1.2*(1.0-status)*g, 1.0);
|
||||
// b = min(status*gl_FragColor.b+1.3*(1.0-status)*b, 1.0);
|
||||
// gl_FragColor = vec4(r, g, b, 1.0);
|
||||
//
|
||||
// //gl_FragColor = blurredImageColor;
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// }
|
||||
// );
|
||||
//
|
||||
//@implementation GPUImageCombinationFilter
|
||||
//
|
||||
//- (id)init {
|
||||
// if (self = [super initWithFragmentShaderFromString:kGPUImageBeautifyFragmentShaderString]) {
|
||||
// smoothDegreeUniform = [filterProgram uniformIndex:@"smoothDegree"];
|
||||
// }
|
||||
// self.intensity = 0.5;
|
||||
// return self;
|
||||
//}
|
||||
//
|
||||
//- (void)setIntensity:(CGFloat)intensity {
|
||||
// _intensity = intensity;
|
||||
// [self setFloat:intensity forUniform:smoothDegreeUniform program:filterProgram];
|
||||
//}
|
||||
//
|
||||
//@end
|
||||
//
|
||||
//@implementation LFGPUImageBeautyFilter
|
||||
//
|
||||
//- (id)init;
|
||||
//{
|
||||
// if (!(self = [super init]))
|
||||
// {
|
||||
// return nil;
|
||||
// }
|
||||
//
|
||||
// // First pass: face smoothing filter
|
||||
//// bilateralFilter = [[GPUImageBilateralFilter alloc] init];
|
||||
//// bilateralFilter.distanceNormalizationFactor = 1.0;
|
||||
//// bilateralFilter.texelSpacingMultiplier = 0.75;
|
||||
//// bilateralFilter.blurRadiusInPixels = 11.0;
|
||||
//// [self addFilter:bilateralFilter];
|
||||
//
|
||||
// bilateralFilter = [[GPUImageGaussianBlurFilter alloc] init];
|
||||
// bilateralFilter.texelSpacingMultiplier = 0.55;
|
||||
// bilateralFilter.blurRadiusInPixels = 13.0;
|
||||
// [self addFilter:bilateralFilter];
|
||||
//
|
||||
// // Second pass: edge detection
|
||||
// cannyEdgeFilter = [[GPUImageCannyEdgeDetectionFilter alloc] init];
|
||||
// [self addFilter:cannyEdgeFilter];
|
||||
//
|
||||
// // Third pass: combination bilateral, edge detection and origin
|
||||
// combinationFilter = [[GPUImageCombinationFilter alloc] init];
|
||||
// [self addFilter:combinationFilter];
|
||||
//
|
||||
// // Adjust HSB
|
||||
// hsbFilter = [[GPUImageHSBFilter alloc] init];
|
||||
// [hsbFilter adjustBrightness:1.0];
|
||||
// [hsbFilter adjustSaturation:1.0];
|
||||
//
|
||||
// [bilateralFilter addTarget:combinationFilter];
|
||||
// [cannyEdgeFilter addTarget:combinationFilter];
|
||||
//
|
||||
// [combinationFilter addTarget:hsbFilter];
|
||||
//
|
||||
// self.initialFilters = [NSArray arrayWithObjects:bilateralFilter,cannyEdgeFilter,combinationFilter,nil];
|
||||
// self.terminalFilter = hsbFilter;
|
||||
//
|
||||
// return self;
|
||||
//}
|
||||
//
|
||||
//#pragma mark -
|
||||
//#pragma mark GPUImageInput protocol
|
||||
//
|
||||
//- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
|
||||
//{
|
||||
// for (GPUImageOutput<GPUImageInput> *currentFilter in self.initialFilters)
|
||||
// {
|
||||
// if (currentFilter != self.inputFilterToIgnoreForUpdates)
|
||||
// {
|
||||
// if (currentFilter == combinationFilter) {
|
||||
// textureIndex = 2;
|
||||
// }
|
||||
// [currentFilter newFrameReadyAtTime:frameTime atIndex:textureIndex];
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
|
||||
//{
|
||||
// for (GPUImageOutput<GPUImageInput> *currentFilter in self.initialFilters)
|
||||
// {
|
||||
// if (currentFilter == combinationFilter) {
|
||||
// textureIndex = 2;
|
||||
// }
|
||||
// [currentFilter setInputFramebuffer:newInputFramebuffer atIndex:textureIndex];
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//@end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
// 真正的上传地址 token等
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "LFLiveAudioConfiguration.h"
|
||||
#import "LFLiveVideoConfiguration.h"
|
||||
|
||||
/// 流状态
|
||||
typedef NS_ENUM(NSUInteger, LFLiveState){
|
||||
@@ -32,6 +34,17 @@ typedef NS_ENUM(NSUInteger,LFLiveSocketErrorCode) {
|
||||
|
||||
@interface LFLiveStreamInfo : NSObject
|
||||
|
||||
@property (nonatomic, copy) NSString *streamId;
|
||||
|
||||
#pragma mark -- FLV
|
||||
@property (nonatomic, copy) NSString *host;
|
||||
@property (nonatomic, assign) NSInteger port;
|
||||
#pragma mark -- RTMP
|
||||
@property (nonatomic, copy) NSString *url; ///< 上传地址 (RTMP用就好了)
|
||||
///音频配置
|
||||
@property (nonatomic, strong) LFLiveAudioConfiguration *audioConfiguration;
|
||||
///视频配置
|
||||
@property (nonatomic, strong) LFLiveVideoConfiguration *videoConfiguration;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// LFFlvPackage.h
|
||||
// LFLiveKit
|
||||
//
|
||||
// Created by 倾慕 on 16/5/2.
|
||||
// Copyright © 2016年 倾慕. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LFStreamPackage.h"
|
||||
|
||||
@interface LFFlvPackage : NSObject<LFStreamPackage>
|
||||
|
||||
#pragma mark - Initializer
|
||||
///=============================================================================
|
||||
/// @name Initializer
|
||||
///=============================================================================
|
||||
- (nullable instancetype)init UNAVAILABLE_ATTRIBUTE;
|
||||
+ (nullable instancetype)new UNAVAILABLE_ATTRIBUTE;
|
||||
|
||||
@end
|
||||
@@ -1,345 +0,0 @@
|
||||
//
|
||||
// LFFlvPackage.m
|
||||
// LFLiveKit
|
||||
//
|
||||
// Created by 倾慕 on 16/5/2.
|
||||
// Copyright © 2016年 倾慕. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LFFlvPackage.h"
|
||||
#include "flv/flv.h"
|
||||
#include "flv/info.h"
|
||||
|
||||
#define kTagLength (4)
|
||||
#define kAVCPacketHeaderSize (5)
|
||||
static const byte kAudioDataHeader = 0xAF;
|
||||
#define swap_uint32_ htonl
|
||||
|
||||
@interface LFFlvPackage (){
|
||||
dispatch_semaphore_t _lock;
|
||||
NSData *_sps;
|
||||
NSData *_pps;
|
||||
NSData *_spec;
|
||||
CGSize _videoSize;
|
||||
FILE *fp;
|
||||
BOOL enabledWriteVideoFile;
|
||||
BOOL enabledWriteFlvHeaderVideoFile;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation LFFlvPackage
|
||||
|
||||
- (instancetype)initWithVideoSize:(CGSize)videoSize{
|
||||
if(CGSizeEqualToSize(videoSize, CGSizeZero)) @throw [NSException exceptionWithName:@"LFFlvPackage init error" reason:@"video size is zero" userInfo:nil];
|
||||
if(self = [super init]){
|
||||
_videoSize = videoSize;
|
||||
_lock = dispatch_semaphore_create(1);
|
||||
#ifdef DEBUG
|
||||
enabledWriteVideoFile = NO;
|
||||
[self initForFilePath];
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc{
|
||||
|
||||
}
|
||||
|
||||
#pragma mark -- LFStreamPackage Delegate
|
||||
- (NSData*)aaCPacket:(LFAudioFrame*)audioFrame{
|
||||
NSMutableData *result = [[NSMutableData alloc] init];
|
||||
|
||||
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
|
||||
|
||||
if(!_spec){
|
||||
_spec = audioFrame.audioInfo;
|
||||
}
|
||||
|
||||
if(!_sps || !_pps){
|
||||
dispatch_semaphore_signal(_lock);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// write audio data
|
||||
uint32 kAACPacketSize = 2;
|
||||
|
||||
NSInteger buffer_size = kAACPacketSize + audioFrame.data.length + FLV_TAG_SIZE;
|
||||
NSInteger packet_size = buffer_size + kTagLength;
|
||||
|
||||
[result appendData:[[self class] flvTagHeader:FLV_TAG_TYPE_AUDIO size:(int32_t)audioFrame.data.length + kAACPacketSize timeStamp:(uint32)audioFrame.timestamp]];
|
||||
|
||||
byte format[2] = { kAudioDataHeader, 0x01};
|
||||
[result appendBytes:format length:sizeof(format)];
|
||||
[result appendData:audioFrame.data];
|
||||
|
||||
uint32 pre_size = swap_uint32_(packet_size-4);
|
||||
[result appendBytes:&pre_size length:sizeof(uint32)];
|
||||
|
||||
audioFrame.header = [[self class] flvHeads:_videoSize.width videoHeight:_videoSize.height sps:_sps pps:_pps audioHeader:_spec];
|
||||
if(enabledWriteVideoFile){
|
||||
if(!enabledWriteFlvHeaderVideoFile){
|
||||
enabledWriteFlvHeaderVideoFile = YES;
|
||||
fwrite(audioFrame.header.bytes, 1,audioFrame.header.length,self->fp);
|
||||
}
|
||||
}
|
||||
|
||||
if(enabledWriteVideoFile) {
|
||||
fwrite(result.bytes, 1, result.length,self->fp);
|
||||
}
|
||||
|
||||
dispatch_semaphore_signal(_lock);
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSData*)h264Packet:(LFVideoFrame*)videoFrame{
|
||||
NSMutableData *result = [[NSMutableData alloc] init];
|
||||
|
||||
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
|
||||
|
||||
if(!_sps || !_pps){
|
||||
_sps = videoFrame.sps;
|
||||
_pps = videoFrame.pps;
|
||||
}
|
||||
|
||||
if(!_spec){
|
||||
dispatch_semaphore_signal(_lock);
|
||||
return nil;
|
||||
}
|
||||
|
||||
videoFrame.header = [[self class] flvHeads:_videoSize.width videoHeight:_videoSize.height sps:_sps pps:_pps audioHeader:_spec];
|
||||
if(enabledWriteVideoFile){
|
||||
if(!enabledWriteFlvHeaderVideoFile){
|
||||
enabledWriteFlvHeaderVideoFile = YES;
|
||||
fwrite(videoFrame.header.bytes, 1,videoFrame.header.length,self->fp);
|
||||
}
|
||||
}
|
||||
|
||||
// write video data
|
||||
// Size + buffer size(4 bytes)
|
||||
uint32 kAVCPacketSize = kAVCPacketHeaderSize + 4;
|
||||
|
||||
size_t buffer_size = kAVCPacketSize + videoFrame.data.length + FLV_TAG_SIZE;
|
||||
size_t packet_size = buffer_size + kTagLength;
|
||||
|
||||
[result appendData:[[self class] flvTagHeader:FLV_TAG_TYPE_VIDEO size:(int32_t)videoFrame.data.length + kAVCPacketSize timeStamp:(uint32)videoFrame.timestamp]];
|
||||
[result appendData:[[self class] h264PacketHeader:videoFrame.isKeyFrame nalu:true]];
|
||||
|
||||
// write length
|
||||
size_t size = videoFrame.data.length;
|
||||
byte length[4] = { 0x00, 0x00, 0x00, 0x00 };
|
||||
length[0] = (size >> 24) & 0xff;
|
||||
length[1] = (size >> 16) & 0xff;
|
||||
length[2] = (size >> 8) & 0xff;
|
||||
length[3] = (size >> 0) & 0xff;
|
||||
[result appendBytes:length length:sizeof(length)];
|
||||
|
||||
// write tag data
|
||||
[result appendData:videoFrame.data];
|
||||
|
||||
uint32 pre_size = swap_uint32_(packet_size-4);
|
||||
[result appendBytes:&pre_size length:sizeof(uint32)];
|
||||
|
||||
if(enabledWriteVideoFile) {
|
||||
fwrite(result.bytes, 1, result.length,self->fp);
|
||||
}
|
||||
|
||||
dispatch_semaphore_signal(_lock);
|
||||
return result;
|
||||
}
|
||||
|
||||
#pragma mark -- FLV
|
||||
int stream_buffer_write_offset = 0;
|
||||
static size_t stream_buffer_write(const void * in_buffer, size_t size, void * user_data) {
|
||||
memcpy(user_data+stream_buffer_write_offset, in_buffer, size);
|
||||
stream_buffer_write_offset += size;
|
||||
return size;
|
||||
}
|
||||
|
||||
+ (NSData*)flvHeader{
|
||||
NSMutableData *result = [[NSMutableData alloc] init];
|
||||
// 写入 flv header信息 /*<464c5601 05000000 09000000 00>*/
|
||||
flv_header header = { };
|
||||
uint32_be offset = swap_uint32_(FLV_HEADER_SIZE);
|
||||
byte extend[kTagLength] = { 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
[result appendBytes:FLV_SIGNATURE length:sizeof(header.signature)];
|
||||
uint8 version[] = {FLV_VERSION};
|
||||
[result appendBytes:&version length:1];
|
||||
uint8 flag[] = {FLV_FLAG_VIDEO | FLV_FLAG_AUDIO};
|
||||
[result appendBytes:&flag length:1];
|
||||
[result appendBytes:&offset length:sizeof(uint32_be)];
|
||||
[result appendBytes:extend length:kTagLength];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (NSData*)flvTagHeader:(uint8)type size:(uint32)size timeStamp:(uint32)timeStamp{
|
||||
flv_tag tag;
|
||||
tag.type = type;
|
||||
tag.body_length = uint32_to_uint24_be(size);
|
||||
flv_tag_set_timestamp(&tag, timeStamp);
|
||||
tag.stream_id = uint32_to_uint24_be(0);
|
||||
|
||||
return [NSData dataWithBytes:&tag length:FLV_TAG_SIZE];
|
||||
}
|
||||
|
||||
+ (NSData*)h264PacketHeader:(BOOL)keyFrame nalu:(BOOL)nalu{
|
||||
byte header[kAVCPacketHeaderSize] = { 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
header[0] = (keyFrame ? 0x10 : 0x20) | 0x07;
|
||||
header[1] = nalu ? 0x01 : 0x00; // 1: AVC NALU 0: AVC sequence header
|
||||
// 后三个字节为Composition time,在AVC中无用
|
||||
return [NSData dataWithBytes:header length:sizeof(header)];
|
||||
}
|
||||
|
||||
+ (NSData*)metaData:(NSInteger)width height:(NSInteger)height{
|
||||
NSMutableData *result = [[NSMutableData alloc] init];
|
||||
|
||||
flv_metadata meta;
|
||||
meta.on_metadata_name = amf_str("onMetaData");
|
||||
meta.on_metadata = amf_associative_array_new();
|
||||
amf_associative_array_add(meta.on_metadata, "width",
|
||||
amf_number_new(width));
|
||||
amf_associative_array_add(meta.on_metadata, "height",
|
||||
amf_number_new(height));
|
||||
amf_associative_array_add(meta.on_metadata, "videocodecid",
|
||||
amf_number_new((number64)FLV_VIDEO_TAG_CODEC_AVC));
|
||||
//usage = base::IntToString(params_.audio_sample_rate);
|
||||
//amf_associative_array_add(meta.on_metadata, "audiosamplerate",
|
||||
// amf_str(usage.c_str()));
|
||||
//usage = base::IntToString(params_.audio_sample_size);
|
||||
//amf_associative_array_add(meta.on_metadata, "audiosamplesize",
|
||||
// amf_str(usage.c_str()));
|
||||
amf_associative_array_add(meta.on_metadata, "stereo", amf_boolean_new(1)); // 对AAC格式: 总为 1
|
||||
amf_associative_array_add(meta.on_metadata, "audiocodecid",
|
||||
amf_number_new((number64)FLV_AUDIO_TAG_SOUND_FORMAT_AAC));
|
||||
// create the onMetaData tag
|
||||
uint32 on_metadata_name_size = (uint32)amf_data_size(meta.on_metadata_name);
|
||||
uint32 on_metadata_size = (uint32)amf_data_size(meta.on_metadata);
|
||||
uint32 meta_size = on_metadata_name_size + on_metadata_size;
|
||||
|
||||
size_t buffer_size = meta_size + FLV_TAG_SIZE;
|
||||
size_t packet_size = true ? buffer_size + kTagLength : buffer_size;
|
||||
[result appendData:[[self class] flvTagHeader:FLV_TAG_TYPE_META size:meta_size timeStamp:0]];
|
||||
|
||||
byte metaName[1024] = {0};
|
||||
byte metaData[1024] = {0};
|
||||
|
||||
stream_buffer_write_offset = 0;
|
||||
size_t metanamelen = amf_data_write(meta.on_metadata_name, stream_buffer_write, metaName);
|
||||
|
||||
stream_buffer_write_offset = 0;
|
||||
size_t metalen = amf_data_write(meta.on_metadata, stream_buffer_write, metaData);
|
||||
|
||||
amf_data_free(meta.on_metadata_name);
|
||||
amf_data_free(meta.on_metadata);
|
||||
|
||||
[result appendBytes:metaName length:metanamelen];
|
||||
[result appendBytes:metaData length:metalen];
|
||||
uint32 pre_size = swap_uint32_(packet_size-4);//为解决第一个pretagsize多了4个而减去4
|
||||
[result appendBytes:&pre_size length:sizeof(uint32)];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (NSData*)flvTagWithVideoHeader:(NSData*)sps pps:(NSData*)pps{
|
||||
NSMutableData *result = [[NSMutableData alloc] init];
|
||||
// 封装AVC sequence header
|
||||
const size_t kExtendSize = 11;
|
||||
size_t buffer_size = sps.length + pps.length + kExtendSize;
|
||||
|
||||
// AVCPacket header size
|
||||
size_t body_size = kAVCPacketHeaderSize + buffer_size;
|
||||
size_t packet_size = body_size + FLV_TAG_SIZE;
|
||||
// AVCDecoderConfigurationRecord
|
||||
[result appendData:[[self class] flvTagHeader:FLV_TAG_TYPE_VIDEO size:(UInt32)body_size timeStamp:0]];
|
||||
[result appendData:[[self class] h264PacketHeader:YES nalu:NO]];
|
||||
|
||||
uint8 configuration1[] = {0x01};
|
||||
[result appendBytes:&configuration1 length:1];
|
||||
[result appendBytes:&sps.bytes[1] length:1];
|
||||
[result appendBytes:&sps.bytes[2] length:1];
|
||||
[result appendBytes:&sps.bytes[3] length:1];
|
||||
uint8 configuration2[] = {0xff};
|
||||
[result appendBytes:&configuration2 length:1];
|
||||
|
||||
// sps
|
||||
uint8 sps1[] = {0xe1};
|
||||
[result appendBytes:&sps1 length:1];
|
||||
uint8 sps2[] = {(sps.length >> 8) & 0xff};
|
||||
[result appendBytes:&sps2 length:1];
|
||||
uint8 sps3[] = {sps.length & 0xff};
|
||||
[result appendBytes:&sps3 length:1];
|
||||
[result appendBytes:sps.bytes length:sps.length];
|
||||
|
||||
|
||||
// pps
|
||||
uint8 pps1[] = {0x01};
|
||||
[result appendBytes:&pps1 length:1];
|
||||
uint8 pps2[] = {(pps.length >> 8) & 0xff};
|
||||
[result appendBytes:&pps2 length:1];
|
||||
uint8 pps3[] = {pps.length & 0xff};
|
||||
[result appendBytes:&pps3 length:1];
|
||||
[result appendBytes:pps.bytes length:pps.length];
|
||||
|
||||
uint32 pre_size = swap_uint32_(packet_size);
|
||||
[result appendBytes:&pre_size length:sizeof(uint32)];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (NSData*)flvTagWithAudioHeader:(NSData*)audioInfo timeStamp:(uint32)timeStamp{
|
||||
NSMutableData *result = [[NSMutableData alloc] init];
|
||||
const size_t kAACPacketHeaderSize = 2;
|
||||
|
||||
size_t body_size = kAACPacketHeaderSize + audioInfo.length;
|
||||
size_t packet_size = body_size + FLV_TAG_SIZE;
|
||||
|
||||
[result appendData:[[self class] flvTagHeader:FLV_TAG_TYPE_AUDIO size:(UInt32)body_size timeStamp:timeStamp]];
|
||||
|
||||
byte format[kAACPacketHeaderSize] = { kAudioDataHeader, 0x01};
|
||||
format[1] = 0x00;
|
||||
[result appendBytes:format length:sizeof(format)];
|
||||
[result appendBytes:audioInfo.bytes length:audioInfo.length];
|
||||
|
||||
uint32 pre_size = swap_uint32_(packet_size);
|
||||
[result appendBytes:&pre_size length:sizeof(uint32)];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (NSData*)flvHeads:(NSInteger)videoWidth videoHeight:(NSInteger)videoHeight sps:(NSData*)sps pps:(NSData*)pps audioHeader:(NSData*)audioHeader{
|
||||
NSMutableData *result = [[NSMutableData alloc] init];
|
||||
// 写FLV头
|
||||
[result appendData:[[self class] flvHeader]];
|
||||
// 写 Meta 相关信息
|
||||
[result appendData:[[self class] metaData:videoWidth height:videoHeight]];
|
||||
// 写音频编码头信息
|
||||
[result appendData:[[self class] flvTagWithAudioHeader:audioHeader timeStamp:0]];
|
||||
// 写视频编码头信息
|
||||
[result appendData:[[self class] flvTagWithVideoHeader:sps pps:pps]];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -- Debug.. store video to local
|
||||
- (void)initForFilePath{
|
||||
NSString *path = [self GetFilePathByfileName:"flv_publish_x1.flv"];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
|
||||
self->fp = fopen([path cStringUsingEncoding:NSUTF8StringEncoding],"wb");
|
||||
}
|
||||
|
||||
- (NSString*)GetFilePathByfileName:(char*)filename{
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask,YES);
|
||||
NSString *documentsDirectory = [paths objectAtIndex:0];
|
||||
NSString *strName = [NSString stringWithFormat:@"%s",filename];
|
||||
|
||||
NSString *writablePath = [documentsDirectory stringByAppendingPathComponent:strName];
|
||||
|
||||
|
||||
return writablePath;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,21 +0,0 @@
|
||||
//
|
||||
// LFStreamPackage.h
|
||||
// LFLiveKit
|
||||
//
|
||||
// Created by 倾慕 on 16/5/2.
|
||||
// Copyright © 2016年 倾慕. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "LFAudioFrame.h"
|
||||
#import "LFVideoFrame.h"
|
||||
|
||||
/// 编码器抽象的接口
|
||||
@protocol LFStreamPackage <NSObject>
|
||||
@required
|
||||
- (nullable instancetype)initWithVideoSize:(CGSize)videoSize;
|
||||
- (nullable NSData*)aaCPacket:(nullable LFAudioFrame*)audioFrame;
|
||||
- (nullable NSData*)h264Packet:(nullable LFVideoFrame*)videoFrame;
|
||||
@end
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
/*
|
||||
$Id: amf.h 231 2011-06-27 13:46:19Z marc.noirot $
|
||||
|
||||
FLV Metadata updater
|
||||
|
||||
Copyright (C) 2007-2012 Marc Noirot <marc.noirot AT gmail.com>
|
||||
|
||||
This file is part of FLVMeta.
|
||||
|
||||
FLVMeta is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FLVMeta is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FLVMeta; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#ifndef __AMF_H__
|
||||
#define __AMF_H__
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/* AMF data types */
|
||||
#define AMF_TYPE_NUMBER ((byte)0x00)
|
||||
#define AMF_TYPE_BOOLEAN ((byte)0x01)
|
||||
#define AMF_TYPE_STRING ((byte)0x02)
|
||||
#define AMF_TYPE_OBJECT ((byte)0x03)
|
||||
#define AMF_TYPE_NULL ((byte)0x05)
|
||||
#define AMF_TYPE_UNDEFINED ((byte)0x06)
|
||||
/* #define AMF_TYPE_REFERENCE ((byte)0x07) */
|
||||
#define AMF_TYPE_ASSOCIATIVE_ARRAY ((byte)0x08)
|
||||
#define AMF_TYPE_END ((byte)0x09)
|
||||
#define AMF_TYPE_ARRAY ((byte)0x0A)
|
||||
#define AMF_TYPE_DATE ((byte)0x0B)
|
||||
/* #define AMF_TYPE_SIMPLEOBJECT ((byte)0x0D) */
|
||||
#define AMF_TYPE_XML ((byte)0x0F)
|
||||
#define AMF_TYPE_CLASS ((byte)0x10)
|
||||
|
||||
/* AMF error codes */
|
||||
#define AMF_ERROR_OK ((byte)0x00)
|
||||
#define AMF_ERROR_EOF ((byte)0x01)
|
||||
#define AMF_ERROR_UNKNOWN_TYPE ((byte)0x02)
|
||||
#define AMF_ERROR_END_TAG ((byte)0x03)
|
||||
#define AMF_ERROR_NULL_POINTER ((byte)0x04)
|
||||
#define AMF_ERROR_MEMORY ((byte)0x05)
|
||||
#define AMF_ERROR_UNSUPPORTED_TYPE ((byte)0x06)
|
||||
|
||||
typedef struct __amf_node * p_amf_node;
|
||||
|
||||
/* string type */
|
||||
typedef struct __amf_string {
|
||||
uint16 size;
|
||||
byte * mbstr;
|
||||
} amf_string;
|
||||
|
||||
/* array type */
|
||||
typedef struct __amf_list {
|
||||
uint32 size;
|
||||
p_amf_node first_element;
|
||||
p_amf_node last_element;
|
||||
} amf_list;
|
||||
|
||||
/* date type */
|
||||
typedef struct __amf_date {
|
||||
number64 milliseconds;
|
||||
sint16 timezone;
|
||||
} amf_date;
|
||||
|
||||
/* XML string type */
|
||||
typedef struct __amf_xmlstring {
|
||||
uint32 size;
|
||||
byte * mbstr;
|
||||
} amf_xmlstring;
|
||||
|
||||
/* class type */
|
||||
typedef struct __amf_class {
|
||||
amf_string name;
|
||||
amf_list elements;
|
||||
} amf_class;
|
||||
|
||||
/* structure encapsulating the various AMF objects */
|
||||
typedef struct __amf_data {
|
||||
byte type;
|
||||
byte error_code;
|
||||
union {
|
||||
number64 number_data;
|
||||
uint8 boolean_data;
|
||||
amf_string string_data;
|
||||
amf_list list_data;
|
||||
amf_date date_data;
|
||||
amf_xmlstring xmlstring_data;
|
||||
amf_class class_data;
|
||||
};
|
||||
} amf_data;
|
||||
|
||||
/* node used in lists, relies on amf_data */
|
||||
typedef struct __amf_node {
|
||||
amf_data * data;
|
||||
p_amf_node prev;
|
||||
p_amf_node next;
|
||||
} amf_node;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/* Pluggable backend support */
|
||||
typedef size_t (*amf_read_proc)(void * out_buffer, size_t size, void * user_data);
|
||||
typedef size_t (*amf_write_proc)(const void * in_buffer, size_t size, void * user_data);
|
||||
|
||||
/* read AMF data */
|
||||
amf_data * amf_data_read(amf_read_proc read_proc, void * user_data);
|
||||
|
||||
/* write AMF data */
|
||||
size_t amf_data_write(const amf_data * data, amf_write_proc write_proc, void * user_data);
|
||||
|
||||
/* generic functions */
|
||||
|
||||
/* allocate an AMF data object */
|
||||
amf_data * amf_data_new(byte type);
|
||||
/* load AMF data from buffer */
|
||||
amf_data * amf_data_buffer_read(byte * buffer, size_t maxbytes);
|
||||
/* load AMF data from stream */
|
||||
amf_data * amf_data_file_read(FILE * stream);
|
||||
/* AMF data size */
|
||||
size_t amf_data_size(const amf_data * data);
|
||||
/* write encoded AMF data into a buffer */
|
||||
size_t amf_data_buffer_write(amf_data * data, byte * buffer, size_t maxbytes);
|
||||
/* write encoded AMF data into a stream */
|
||||
size_t amf_data_file_write(const amf_data * data, FILE * stream);
|
||||
/* get the type of AMF data */
|
||||
byte amf_data_get_type(const amf_data * data);
|
||||
/* get the error code of AMF data */
|
||||
byte amf_data_get_error_code(const amf_data * data);
|
||||
/* return a new copy of AMF data */
|
||||
amf_data * amf_data_clone(const amf_data * data);
|
||||
/* release the memory of AMF data */
|
||||
void amf_data_free(amf_data * data);
|
||||
/* dump AMF data into a stream as text */
|
||||
void amf_data_dump(FILE * stream, const amf_data * data, int indent_level);
|
||||
|
||||
/* return a null AMF object with the specified error code attached to it */
|
||||
amf_data * amf_data_error(byte error_code);
|
||||
|
||||
/* number functions */
|
||||
amf_data * amf_number_new(number64 value);
|
||||
amf_data * amf_number_double(double value);
|
||||
number64 amf_number_get_value(const amf_data * data);
|
||||
void amf_number_set_value(amf_data * data, number64 value);
|
||||
|
||||
/* boolean functions */
|
||||
amf_data * amf_boolean_new(uint8 value);
|
||||
uint8 amf_boolean_get_value(const amf_data * data);
|
||||
void amf_boolean_set_value(amf_data * data, uint8 value);
|
||||
|
||||
/* string functions */
|
||||
amf_data * amf_string_new(byte * str, uint16 size);
|
||||
amf_data * amf_str(const char * str);
|
||||
uint16 amf_string_get_size(const amf_data * data);
|
||||
byte * amf_string_get_bytes(const amf_data * data);
|
||||
|
||||
/* object functions */
|
||||
amf_data * amf_object_new(void);
|
||||
uint32 amf_object_size(const amf_data * data);
|
||||
amf_data * amf_object_add(amf_data * data, const char * name, amf_data * element);
|
||||
amf_data * amf_object_get(const amf_data * data, const char * name);
|
||||
amf_data * amf_object_set(amf_data * data, const char * name, amf_data * element);
|
||||
amf_data * amf_object_delete(amf_data * data, const char * name);
|
||||
amf_node * amf_object_first(const amf_data * data);
|
||||
amf_node * amf_object_last(const amf_data * data);
|
||||
amf_node * amf_object_next(amf_node * node);
|
||||
amf_node * amf_object_prev(amf_node * node);
|
||||
amf_data * amf_object_get_name(amf_node * node);
|
||||
amf_data * amf_object_get_data(amf_node * node);
|
||||
|
||||
/* null functions */
|
||||
#define amf_null_new() amf_data_new(AMF_TYPE_NULL)
|
||||
|
||||
/* undefined functions */
|
||||
#define amf_undefined_new() amf_data_new(AMF_TYPE_UNDEFINED)
|
||||
|
||||
/* associative array functions */
|
||||
amf_data * amf_associative_array_new(void);
|
||||
#define amf_associative_array_size(d) amf_object_size(d)
|
||||
#define amf_associative_array_add(d, n, e) amf_object_add(d, n, e)
|
||||
#define amf_associative_array_get(d, n) amf_object_get(d, n)
|
||||
#define amf_associative_array_set(d, n, e) amf_object_set(d, n, e)
|
||||
#define amf_associative_array_delete(d, n) amf_object_delete(d, n)
|
||||
#define amf_associative_array_first(d) amf_object_first(d)
|
||||
#define amf_associative_array_last(d) amf_object_last(d)
|
||||
#define amf_associative_array_next(n) amf_object_next(n)
|
||||
#define amf_associative_array_prev(n) amf_object_prev(n)
|
||||
#define amf_associative_array_get_name(n) amf_object_get_name(n)
|
||||
#define amf_associative_array_get_data(n) amf_object_get_data(n)
|
||||
|
||||
/* array functions */
|
||||
amf_data * amf_array_new(void);
|
||||
uint32 amf_array_size(const amf_data * data);
|
||||
amf_data * amf_array_push(amf_data * data, amf_data * element);
|
||||
amf_data * amf_array_pop(amf_data * data);
|
||||
amf_node * amf_array_first(const amf_data * data);
|
||||
amf_node * amf_array_last(const amf_data * data);
|
||||
amf_node * amf_array_next(amf_node * node);
|
||||
amf_node * amf_array_prev(amf_node * node);
|
||||
amf_data * amf_array_get(amf_node * node);
|
||||
amf_data * amf_array_get_at(const amf_data * data, uint32 n);
|
||||
amf_data * amf_array_delete(amf_data * data, amf_node * node);
|
||||
amf_data * amf_array_insert_before(amf_data * data, amf_node * node, amf_data * element);
|
||||
amf_data * amf_array_insert_after(amf_data * data, amf_node * node, amf_data * element);
|
||||
|
||||
/* date functions */
|
||||
amf_data * amf_date_new(number64 milliseconds, sint16 timezone);
|
||||
number64 amf_date_get_milliseconds(const amf_data * data);
|
||||
sint16 amf_date_get_timezone(const amf_data * data);
|
||||
time_t amf_date_to_time_t(const amf_data * data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* __AMF_H__ */
|
||||
@@ -1,298 +0,0 @@
|
||||
/*
|
||||
$Id: avc.c 231 2011-06-27 13:46:19Z marc.noirot $
|
||||
|
||||
FLV Metadata updater
|
||||
|
||||
Copyright (C) 2007-2012 Marc Noirot <marc.noirot AT gmail.com>
|
||||
|
||||
This file is part of FLVMeta.
|
||||
|
||||
FLVMeta is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FLVMeta is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FLVMeta; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "avc.h"
|
||||
|
||||
/**
|
||||
bit buffer handling
|
||||
*/
|
||||
typedef struct __bit_buffer {
|
||||
byte * start;
|
||||
size_t size;
|
||||
byte * current;
|
||||
uint8 read_bits;
|
||||
} bit_buffer;
|
||||
|
||||
static void skip_bits(bit_buffer * bb, size_t nbits) {
|
||||
bb->current = bb->current + ((nbits + bb->read_bits) / 8);
|
||||
bb->read_bits = (uint8)((bb->read_bits + nbits) % 8);
|
||||
}
|
||||
|
||||
static uint8 get_bit(bit_buffer * bb) {
|
||||
uint8 ret;
|
||||
ret = (*(bb->current) >> (7 - bb->read_bits)) & 0x1;
|
||||
if (bb->read_bits == 7) {
|
||||
bb->read_bits = 0;
|
||||
bb->current++;
|
||||
}
|
||||
else {
|
||||
bb->read_bits++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint32 get_bits(bit_buffer * bb, size_t nbits) {
|
||||
uint32 i, ret;
|
||||
ret = 0;
|
||||
for (i = 0; i < nbits; i++) {
|
||||
ret = (ret << 1) + get_bit(bb);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint32 exp_golomb_ue(bit_buffer * bb) {
|
||||
uint8 bit, significant_bits;
|
||||
significant_bits = 0;
|
||||
bit = get_bit(bb);
|
||||
while (bit == 0) {
|
||||
significant_bits++;
|
||||
bit = get_bit(bb);
|
||||
}
|
||||
return (1 << significant_bits) + get_bits(bb, significant_bits) - 1;
|
||||
}
|
||||
|
||||
static sint32 exp_golomb_se(bit_buffer * bb) {
|
||||
sint32 ret;
|
||||
ret = exp_golomb_ue(bb);
|
||||
if ((ret & 0x1) == 0) {
|
||||
return -(ret >> 1);
|
||||
}
|
||||
else {
|
||||
return (ret + 1) >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* AVC type definitions */
|
||||
|
||||
#define AVC_SEQUENCE_HEADER 0
|
||||
#define AVC_NALU 1
|
||||
#define AVC_END_OF_SEQUENCE 2
|
||||
|
||||
typedef struct __AVCDecoderConfigurationRecord {
|
||||
uint8 configurationVersion;
|
||||
uint8 AVCProfileIndication;
|
||||
uint8 profile_compatibility;
|
||||
uint8 AVCLevelIndication;
|
||||
uint8 lengthSizeMinusOne;
|
||||
uint8 numOfSequenceParameterSets;
|
||||
} AVCDecoderConfigurationRecord;
|
||||
|
||||
int read_avc_decoder_configuration_record(flv_stream * f, AVCDecoderConfigurationRecord * adcr) {
|
||||
if (flv_read_tag_body(f, &adcr->configurationVersion, 1) == 1
|
||||
&& flv_read_tag_body(f, &adcr->AVCProfileIndication, 1) == 1
|
||||
&& flv_read_tag_body(f, &adcr->profile_compatibility, 1) == 1
|
||||
&& flv_read_tag_body(f, &adcr->AVCLevelIndication, 1) == 1
|
||||
&& flv_read_tag_body(f, &adcr->lengthSizeMinusOne, 1) == 1
|
||||
&& flv_read_tag_body(f, &adcr->numOfSequenceParameterSets, 1) == 1) {
|
||||
return FLV_OK;
|
||||
}
|
||||
else {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void parse_scaling_list(uint32 size, bit_buffer * bb) {
|
||||
uint32 last_scale, next_scale, i;
|
||||
sint32 delta_scale;
|
||||
last_scale = 8;
|
||||
next_scale = 8;
|
||||
for (i = 0; i < size; i++) {
|
||||
if (next_scale != 0) {
|
||||
delta_scale = exp_golomb_se(bb);
|
||||
next_scale = (last_scale + delta_scale + 256) % 256;
|
||||
}
|
||||
if (next_scale != 0) {
|
||||
last_scale = next_scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Parses a SPS NALU to retrieve video width and height
|
||||
*/
|
||||
static void parse_sps(byte * sps, size_t sps_size, uint32 * width, uint32 * height) {
|
||||
bit_buffer bb;
|
||||
uint32 profile, pic_order_cnt_type, width_in_mbs, height_in_map_units;
|
||||
uint32 i, size, left, right, top, bottom;
|
||||
uint8 frame_mbs_only_flag;
|
||||
|
||||
bb.start = sps;
|
||||
bb.size = sps_size;
|
||||
bb.current = sps;
|
||||
bb.read_bits = 0;
|
||||
|
||||
/* skip first byte, since we already know we're parsing a SPS */
|
||||
skip_bits(&bb, 8);
|
||||
/* get profile */
|
||||
profile = get_bits(&bb, 8);
|
||||
/* skip 4 bits + 4 zeroed bits + 8 bits = 32 bits = 4 bytes */
|
||||
skip_bits(&bb, 16);
|
||||
|
||||
/* read sps id, first exp-golomb encoded value */
|
||||
exp_golomb_ue(&bb);
|
||||
|
||||
if (profile == 100 || profile == 110 || profile == 122 || profile == 144) {
|
||||
/* chroma format idx */
|
||||
if (exp_golomb_ue(&bb) == 3) {
|
||||
skip_bits(&bb, 1);
|
||||
}
|
||||
/* bit depth luma minus8 */
|
||||
exp_golomb_ue(&bb);
|
||||
/* bit depth chroma minus8 */
|
||||
exp_golomb_ue(&bb);
|
||||
/* Qpprime Y Zero Transform Bypass flag */
|
||||
skip_bits(&bb, 1);
|
||||
/* Seq Scaling Matrix Present Flag */
|
||||
if (get_bit(&bb)) {
|
||||
for (i = 0; i < 8; i++) {
|
||||
/* Seq Scaling List Present Flag */
|
||||
if (get_bit(&bb)) {
|
||||
parse_scaling_list(i < 6 ? 16 : 64, &bb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* log2_max_frame_num_minus4 */
|
||||
exp_golomb_ue(&bb);
|
||||
/* pic_order_cnt_type */
|
||||
pic_order_cnt_type = exp_golomb_ue(&bb);
|
||||
if (pic_order_cnt_type == 0) {
|
||||
/* log2_max_pic_order_cnt_lsb_minus4 */
|
||||
exp_golomb_ue(&bb);
|
||||
}
|
||||
else if (pic_order_cnt_type == 1) {
|
||||
/* delta_pic_order_always_zero_flag */
|
||||
skip_bits(&bb, 1);
|
||||
/* offset_for_non_ref_pic */
|
||||
exp_golomb_se(&bb);
|
||||
/* offset_for_top_to_bottom_field */
|
||||
exp_golomb_se(&bb);
|
||||
size = exp_golomb_ue(&bb);
|
||||
for (i = 0; i < size; i++) {
|
||||
/* offset_for_ref_frame */
|
||||
exp_golomb_se(&bb);
|
||||
}
|
||||
}
|
||||
/* num_ref_frames */
|
||||
exp_golomb_ue(&bb);
|
||||
/* gaps_in_frame_num_value_allowed_flag */
|
||||
skip_bits(&bb, 1);
|
||||
/* pic_width_in_mbs */
|
||||
width_in_mbs = exp_golomb_ue(&bb) + 1;
|
||||
/* pic_height_in_map_units */
|
||||
height_in_map_units = exp_golomb_ue(&bb) + 1;
|
||||
/* frame_mbs_only_flag */
|
||||
frame_mbs_only_flag = get_bit(&bb);
|
||||
if (!frame_mbs_only_flag) {
|
||||
/* mb_adaptive_frame_field */
|
||||
skip_bits(&bb, 1);
|
||||
}
|
||||
/* direct_8x8_inference_flag */
|
||||
skip_bits(&bb, 1);
|
||||
/* frame_cropping */
|
||||
left = right = top = bottom = 0;
|
||||
if (get_bit(&bb)) {
|
||||
left = exp_golomb_ue(&bb) * 2;
|
||||
right = exp_golomb_ue(&bb) * 2;
|
||||
top = exp_golomb_ue(&bb) * 2;
|
||||
bottom = exp_golomb_ue(&bb) * 2;
|
||||
if (!frame_mbs_only_flag) {
|
||||
top *= 2;
|
||||
bottom *= 2;
|
||||
}
|
||||
}
|
||||
/* width */
|
||||
*width = width_in_mbs * 16 - (left + right);
|
||||
/* height */
|
||||
*height = height_in_map_units * 16 - (top + bottom);
|
||||
if (!frame_mbs_only_flag) {
|
||||
*height *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Tries to read the resolution of the current video packet.
|
||||
We assume to be at the first byte of the video data.
|
||||
*/
|
||||
int read_avc_resolution(flv_stream * f, uint32 body_length, uint32 * width, uint32 * height) {
|
||||
byte avc_packet_type;
|
||||
uint24 composition_time;
|
||||
AVCDecoderConfigurationRecord adcr;
|
||||
uint16 sps_size;
|
||||
byte * sps_buffer;
|
||||
|
||||
/* make sure we have enough bytes to read in the current tag */
|
||||
if (body_length < sizeof(byte) + sizeof(uint24) + sizeof(AVCDecoderConfigurationRecord)) {
|
||||
return FLV_OK;
|
||||
}
|
||||
|
||||
/* determine whether we're reading an AVCDecoderConfigurationRecord */
|
||||
if (flv_read_tag_body(f, &avc_packet_type, 1) < 1) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
if (avc_packet_type != AVC_SEQUENCE_HEADER) {
|
||||
return FLV_OK;
|
||||
}
|
||||
|
||||
/* read the composition time */
|
||||
if (flv_read_tag_body(f, &composition_time, sizeof(uint24)) < sizeof(uint24)) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
/* we need to read an AVCDecoderConfigurationRecord */
|
||||
if (read_avc_decoder_configuration_record(f, &adcr) == FLV_ERROR_EOF) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
/* number of SequenceParameterSets */
|
||||
if ((adcr.numOfSequenceParameterSets & 0x1F) == 0) {
|
||||
/* no SPS, return */
|
||||
return FLV_OK;
|
||||
}
|
||||
|
||||
/** read the first SequenceParameterSet found */
|
||||
/* SPS size */
|
||||
if (flv_read_tag_body(f, &sps_size, sizeof(uint16)) < sizeof(uint16)) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
sps_size = swap_uint16(sps_size);
|
||||
|
||||
/* read the SPS entirely */
|
||||
sps_buffer = (byte *) malloc((size_t)sps_size);
|
||||
if (sps_buffer == NULL) {
|
||||
return FLV_ERROR_MEMORY;
|
||||
}
|
||||
if (flv_read_tag_body(f, sps_buffer, (size_t)sps_size) < (size_t)sps_size) {
|
||||
free(sps_buffer);
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
/* parse SPS to determine video resolution */
|
||||
parse_sps(sps_buffer, (size_t)sps_size, width, height);
|
||||
|
||||
free(sps_buffer);
|
||||
return FLV_OK;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
$Id: avc.h 231 2011-06-27 13:46:19Z marc.noirot $
|
||||
|
||||
FLV Metadata updater
|
||||
|
||||
Copyright (C) 2007-2012 Marc Noirot <marc.noirot AT gmail.com>
|
||||
|
||||
This file is part of FLVMeta.
|
||||
|
||||
FLVMeta is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FLVMeta is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FLVMeta; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#ifndef __AVC_H__
|
||||
#define __AVC_H__
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "flv.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
int read_avc_resolution(flv_stream * f, uint32 body_length, uint32 * width, uint32 * height);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* __AVC_H__ */
|
||||
@@ -1,498 +0,0 @@
|
||||
/*
|
||||
$Id: flv.c 231 2011-06-27 13:46:19Z marc.noirot $
|
||||
|
||||
FLV Metadata updater
|
||||
|
||||
Copyright (C) 2007-2012 Marc Noirot <marc.noirot AT gmail.com>
|
||||
|
||||
This file is part of FLVMeta.
|
||||
|
||||
FLVMeta is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FLVMeta is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FLVMeta; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include "flv.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
void flv_tag_set_timestamp(flv_tag * tag, uint32 timestamp) {
|
||||
tag->timestamp = uint32_to_uint24_be(timestamp);
|
||||
tag->timestamp_extended = (uint8)((timestamp & 0xFF000000) >> 24);
|
||||
}
|
||||
|
||||
/* FLV stream functions */
|
||||
flv_stream * flv_open(const char * file) {
|
||||
flv_stream * stream = (flv_stream *) malloc(sizeof(flv_stream));
|
||||
if (stream == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
stream->flvin = fopen(file, "rb");
|
||||
if (stream->flvin == NULL) {
|
||||
free(stream);
|
||||
return NULL;
|
||||
}
|
||||
stream->current_tag_body_length = 0;
|
||||
stream->current_tag_body_overflow = 0;
|
||||
stream->current_tag_offset = 0;
|
||||
stream->state = FLV_STREAM_STATE_START;
|
||||
return stream;
|
||||
}
|
||||
|
||||
int flv_read_header(flv_stream * stream, flv_header * header) {
|
||||
if (stream == NULL
|
||||
|| stream->flvin == NULL
|
||||
|| feof(stream->flvin)
|
||||
|| stream->state != FLV_STREAM_STATE_START) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
if (fread(&header->signature, sizeof(header->signature), 1, stream->flvin) == 0
|
||||
|| fread(&header->version, sizeof(header->version), 1, stream->flvin) == 0
|
||||
|| fread(&header->flags, sizeof(header->flags), 1, stream->flvin) == 0
|
||||
|| fread(&header->offset, sizeof(header->offset), 1, stream->flvin) == 0) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
if (header->signature[0] != 'F'
|
||||
|| header->signature[1] != 'L'
|
||||
|| header->signature[2] != 'V') {
|
||||
return FLV_ERROR_NO_FLV;
|
||||
}
|
||||
|
||||
stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE;
|
||||
return FLV_OK;
|
||||
}
|
||||
|
||||
int flv_read_prev_tag_size(flv_stream * stream, uint32 * prev_tag_size) {
|
||||
uint32_be val;
|
||||
if (stream == NULL
|
||||
|| stream->flvin == NULL
|
||||
|| feof(stream->flvin)) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
/* skip remaining tag body bytes */
|
||||
if (stream->state == FLV_STREAM_STATE_TAG_BODY) {
|
||||
lfs_fseek(stream->flvin, stream->current_tag_offset + FLV_TAG_SIZE + uint24_be_to_uint32(stream->current_tag.body_length), SEEK_SET);
|
||||
stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE;
|
||||
}
|
||||
|
||||
if (stream->state == FLV_STREAM_STATE_PREV_TAG_SIZE) {
|
||||
if (fread(&val, sizeof(uint32_be), 1, stream->flvin) == 0) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
else {
|
||||
stream->state = FLV_STREAM_STATE_TAG;
|
||||
*prev_tag_size = swap_uint32(val);
|
||||
return FLV_OK;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
}
|
||||
|
||||
int flv_read_tag(flv_stream * stream, flv_tag * tag) {
|
||||
if (stream == NULL
|
||||
|| stream->flvin == NULL
|
||||
|| feof(stream->flvin)) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
/* skip header */
|
||||
if (stream->state == FLV_STREAM_STATE_START) {
|
||||
lfs_fseek(stream->flvin, FLV_HEADER_SIZE, SEEK_CUR);
|
||||
stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE;
|
||||
}
|
||||
|
||||
/* skip current tag body */
|
||||
if (stream->state == FLV_STREAM_STATE_TAG_BODY) {
|
||||
lfs_fseek(stream->flvin, stream->current_tag_offset + FLV_TAG_SIZE + uint24_be_to_uint32(stream->current_tag.body_length), SEEK_SET);
|
||||
stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE;
|
||||
}
|
||||
|
||||
/* skip previous tag size */
|
||||
if (stream->state == FLV_STREAM_STATE_PREV_TAG_SIZE) {
|
||||
lfs_fseek(stream->flvin, sizeof(uint32_be), SEEK_CUR);
|
||||
stream->state = FLV_STREAM_STATE_TAG;
|
||||
}
|
||||
|
||||
if (stream->state == FLV_STREAM_STATE_TAG) {
|
||||
stream->current_tag_offset = lfs_ftell(stream->flvin);
|
||||
|
||||
if (fread(&tag->type, sizeof(tag->type), 1, stream->flvin) == 0
|
||||
|| fread(&tag->body_length, sizeof(tag->body_length), 1, stream->flvin) == 0
|
||||
|| fread(&tag->timestamp, sizeof(tag->timestamp), 1, stream->flvin) == 0
|
||||
|| fread(&tag->timestamp_extended, sizeof(tag->timestamp_extended), 1, stream->flvin) == 0
|
||||
|| fread(&tag->stream_id, sizeof(tag->stream_id), 1, stream->flvin) == 0) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
else {
|
||||
memcpy(&stream->current_tag, tag, sizeof(flv_tag));
|
||||
stream->current_tag_body_length = uint24_be_to_uint32(tag->body_length);
|
||||
stream->current_tag_body_overflow = 0;
|
||||
stream->state = FLV_STREAM_STATE_TAG_BODY;
|
||||
return FLV_OK;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
}
|
||||
|
||||
int flv_read_audio_tag(flv_stream * stream, flv_audio_tag * tag) {
|
||||
if (stream == NULL
|
||||
|| stream->flvin == NULL
|
||||
|| feof(stream->flvin)
|
||||
|| stream->state != FLV_STREAM_STATE_TAG_BODY) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
if (stream->current_tag_body_length == 0) {
|
||||
return FLV_ERROR_EMPTY_TAG;
|
||||
}
|
||||
|
||||
if (fread(tag, sizeof(flv_audio_tag), 1, stream->flvin) == 0) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
if (stream->current_tag_body_length >= sizeof(flv_audio_tag)) {
|
||||
stream->current_tag_body_length -= sizeof(flv_audio_tag);
|
||||
}
|
||||
else {
|
||||
stream->current_tag_body_overflow = sizeof(flv_audio_tag) - stream->current_tag_body_length;
|
||||
stream->current_tag_body_length = 0;
|
||||
}
|
||||
|
||||
if (stream->current_tag_body_length == 0) {
|
||||
stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE;
|
||||
if (stream->current_tag_body_overflow > 0) {
|
||||
lfs_fseek(stream->flvin, -(file_offset_t)stream->current_tag_body_overflow, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
|
||||
return FLV_OK;
|
||||
}
|
||||
|
||||
int flv_read_video_tag(flv_stream * stream, flv_video_tag * tag) {
|
||||
if (stream == NULL
|
||||
|| stream->flvin == NULL
|
||||
|| feof(stream->flvin)
|
||||
|| stream->state != FLV_STREAM_STATE_TAG_BODY) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
if (stream->current_tag_body_length == 0) {
|
||||
return FLV_ERROR_EMPTY_TAG;
|
||||
}
|
||||
|
||||
if (fread(tag, sizeof(flv_video_tag), 1, stream->flvin) == 0) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
if (stream->current_tag_body_length >= sizeof(flv_video_tag)) {
|
||||
stream->current_tag_body_length -= sizeof(flv_video_tag);
|
||||
}
|
||||
else {
|
||||
stream->current_tag_body_overflow = sizeof(flv_video_tag) - stream->current_tag_body_length;
|
||||
stream->current_tag_body_length = 0;
|
||||
}
|
||||
|
||||
if (stream->current_tag_body_length == 0) {
|
||||
stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE;
|
||||
if (stream->current_tag_body_overflow > 0) {
|
||||
lfs_fseek(stream->flvin, -(file_offset_t)stream->current_tag_body_overflow, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
|
||||
return FLV_OK;
|
||||
}
|
||||
|
||||
int flv_read_metadata(flv_stream * stream, amf_data ** name, amf_data ** data) {
|
||||
amf_data * d;
|
||||
byte error_code;
|
||||
size_t data_size;
|
||||
|
||||
if (stream == NULL
|
||||
|| stream->flvin == NULL
|
||||
|| feof(stream->flvin)
|
||||
|| stream->state != FLV_STREAM_STATE_TAG_BODY) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
if (stream->current_tag_body_length == 0) {
|
||||
return FLV_ERROR_EMPTY_TAG;
|
||||
}
|
||||
|
||||
/* read metadata name */
|
||||
d = amf_data_file_read(stream->flvin);
|
||||
*name = d;
|
||||
error_code = amf_data_get_error_code(d);
|
||||
if (error_code == AMF_ERROR_EOF) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
else if (error_code != AMF_ERROR_OK) {
|
||||
return FLV_ERROR_INVALID_METADATA_NAME;
|
||||
}
|
||||
|
||||
/* if only name can be read, metadata are invalid */
|
||||
data_size = amf_data_size(d);
|
||||
if (stream->current_tag_body_length > data_size) {
|
||||
stream->current_tag_body_length -= (uint32)data_size;
|
||||
}
|
||||
else {
|
||||
stream->current_tag_body_length = 0;
|
||||
stream->current_tag_body_overflow = (uint32)data_size - stream->current_tag_body_length;
|
||||
|
||||
stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE;
|
||||
if (stream->current_tag_body_overflow > 0) {
|
||||
lfs_fseek(stream->flvin, -(file_offset_t)stream->current_tag_body_overflow, SEEK_CUR);
|
||||
}
|
||||
|
||||
return FLV_ERROR_INVALID_METADATA;
|
||||
}
|
||||
|
||||
/* read metadata contents */
|
||||
d = amf_data_file_read(stream->flvin);
|
||||
*data = d;
|
||||
error_code = amf_data_get_error_code(d);
|
||||
if (error_code == AMF_ERROR_EOF) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
if (error_code != AMF_ERROR_OK) {
|
||||
return FLV_ERROR_INVALID_METADATA;
|
||||
}
|
||||
|
||||
data_size = amf_data_size(d);
|
||||
if (stream->current_tag_body_length >= data_size) {
|
||||
stream->current_tag_body_length -= (uint32)data_size;
|
||||
}
|
||||
else {
|
||||
stream->current_tag_body_overflow = (uint32)data_size - stream->current_tag_body_length;
|
||||
stream->current_tag_body_length = 0;
|
||||
}
|
||||
|
||||
if (stream->current_tag_body_length == 0) {
|
||||
stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE;
|
||||
if (stream->current_tag_body_overflow > 0) {
|
||||
lfs_fseek(stream->flvin, -(file_offset_t)stream->current_tag_body_overflow, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
|
||||
return FLV_OK;
|
||||
}
|
||||
|
||||
size_t flv_read_tag_body(flv_stream * stream, void * buffer, size_t buffer_size) {
|
||||
size_t bytes_number;
|
||||
|
||||
if (stream == NULL
|
||||
|| stream->flvin == NULL
|
||||
|| feof(stream->flvin)
|
||||
|| stream->state != FLV_STREAM_STATE_TAG_BODY) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bytes_number = (buffer_size > stream->current_tag_body_length) ? stream->current_tag_body_length : buffer_size;
|
||||
bytes_number = fread(buffer, sizeof(byte), bytes_number, stream->flvin);
|
||||
|
||||
stream->current_tag_body_length -= (uint32)bytes_number;
|
||||
|
||||
if (stream->current_tag_body_length == 0) {
|
||||
stream->state = FLV_STREAM_STATE_PREV_TAG_SIZE;
|
||||
}
|
||||
|
||||
return bytes_number;
|
||||
}
|
||||
|
||||
file_offset_t flv_get_current_tag_offset(flv_stream * stream) {
|
||||
return (stream != NULL) ? stream->current_tag_offset : 0;
|
||||
}
|
||||
|
||||
file_offset_t flv_get_offset(flv_stream * stream) {
|
||||
return (stream != NULL) ? lfs_ftell(stream->flvin) : 0;
|
||||
}
|
||||
|
||||
void flv_reset(flv_stream * stream) {
|
||||
/* go back to beginning of file */
|
||||
if (stream != NULL && stream->flvin != NULL) {
|
||||
stream->current_tag_body_length = 0;
|
||||
stream->current_tag_offset = 0;
|
||||
stream->state = FLV_STREAM_STATE_START;
|
||||
|
||||
lfs_fseek(stream->flvin, 0, SEEK_SET);
|
||||
}
|
||||
}
|
||||
|
||||
void flv_close(flv_stream * stream) {
|
||||
if (stream != NULL) {
|
||||
if (stream->flvin != NULL) {
|
||||
fclose(stream->flvin);
|
||||
}
|
||||
free(stream);
|
||||
}
|
||||
}
|
||||
|
||||
/* FLV stdio writing helper functions */
|
||||
size_t flv_write_header(FILE * out, const flv_header * header) {
|
||||
if (fwrite(&header->signature, sizeof(header->signature), 1, out) == 0)
|
||||
return 0;
|
||||
if (fwrite(&header->version, sizeof(header->version), 1, out) == 0)
|
||||
return 0;
|
||||
if (fwrite(&header->flags, sizeof(header->flags), 1, out) == 0)
|
||||
return 0;
|
||||
if (fwrite(&header->offset, sizeof(header->offset), 1, out) == 0)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t flv_write_tag(FILE * out, const flv_tag * tag) {
|
||||
if (fwrite(&tag->type, sizeof(tag->type), 1, out) == 0)
|
||||
return 0;
|
||||
|
||||
if (fwrite(&tag->body_length, sizeof(tag->body_length), 1, out) == 0)
|
||||
return 0;
|
||||
|
||||
if (fwrite(&tag->timestamp, sizeof(tag->timestamp), 1, out) == 0)
|
||||
return 0;
|
||||
if (fwrite(&tag->timestamp_extended, sizeof(tag->timestamp_extended), 1, out) == 0)
|
||||
return 0;
|
||||
if (fwrite(&tag->stream_id, sizeof(tag->stream_id), 1, out) == 0)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* FLV event based parser */
|
||||
int flv_parse(const char * file, flv_parser * parser) {
|
||||
flv_header header;
|
||||
flv_tag tag;
|
||||
flv_audio_tag at;
|
||||
flv_video_tag vt;
|
||||
amf_data * name, * data;
|
||||
uint32 prev_tag_size;
|
||||
int retval;
|
||||
|
||||
if (parser == NULL) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
parser->stream = flv_open(file);
|
||||
if (parser->stream == NULL) {
|
||||
return FLV_ERROR_OPEN_READ;
|
||||
}
|
||||
|
||||
retval = flv_read_header(parser->stream, &header);
|
||||
if (retval != FLV_OK) {
|
||||
flv_close(parser->stream);
|
||||
return retval;
|
||||
}
|
||||
|
||||
if (parser->on_header != NULL) {
|
||||
retval = parser->on_header(&header, parser);
|
||||
if (retval != FLV_OK) {
|
||||
flv_close(parser->stream);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
while (flv_read_tag(parser->stream, &tag) == FLV_OK) {
|
||||
if (parser->on_tag != NULL) {
|
||||
retval = parser->on_tag(&tag, parser);
|
||||
if (retval != FLV_OK) {
|
||||
flv_close(parser->stream);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
if (tag.type == FLV_TAG_TYPE_AUDIO) {
|
||||
retval = flv_read_audio_tag(parser->stream, &at);
|
||||
if (retval == FLV_ERROR_EOF) {
|
||||
flv_close(parser->stream);
|
||||
return retval;
|
||||
}
|
||||
if (retval != FLV_ERROR_EMPTY_TAG && parser->on_audio_tag != NULL) {
|
||||
retval = parser->on_audio_tag(&tag, at, parser);
|
||||
if (retval != FLV_OK) {
|
||||
flv_close(parser->stream);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (tag.type == FLV_TAG_TYPE_VIDEO) {
|
||||
retval = flv_read_video_tag(parser->stream, &vt);
|
||||
if (retval == FLV_ERROR_EOF) {
|
||||
flv_close(parser->stream);
|
||||
return retval;
|
||||
}
|
||||
if (retval != FLV_ERROR_EMPTY_TAG && parser->on_video_tag != NULL) {
|
||||
retval = parser->on_video_tag(&tag, vt, parser);
|
||||
if (retval != FLV_OK) {
|
||||
flv_close(parser->stream);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (tag.type == FLV_TAG_TYPE_META) {
|
||||
name = data = NULL;
|
||||
retval = flv_read_metadata(parser->stream, &name, &data);
|
||||
if (retval == FLV_ERROR_EOF) {
|
||||
amf_data_free(name);
|
||||
amf_data_free(data);
|
||||
flv_close(parser->stream);
|
||||
return retval;
|
||||
}
|
||||
else if (retval == FLV_OK && parser->on_metadata_tag != NULL) {
|
||||
retval = parser->on_metadata_tag(&tag, name, data, parser);
|
||||
if (retval != FLV_OK) {
|
||||
amf_data_free(name);
|
||||
amf_data_free(data);
|
||||
flv_close(parser->stream);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
amf_data_free(name);
|
||||
amf_data_free(data);
|
||||
}
|
||||
else {
|
||||
if (parser->on_unknown_tag != NULL) {
|
||||
retval = parser->on_unknown_tag(&tag, parser);
|
||||
if (retval != FLV_OK) {
|
||||
flv_close(parser->stream);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
}
|
||||
retval = flv_read_prev_tag_size(parser->stream, &prev_tag_size);
|
||||
if (retval != FLV_OK) {
|
||||
flv_close(parser->stream);
|
||||
return retval;
|
||||
}
|
||||
if (parser->on_prev_tag_size != NULL) {
|
||||
retval = parser->on_prev_tag_size(prev_tag_size, parser);
|
||||
if (retval != FLV_OK) {
|
||||
flv_close(parser->stream);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parser->on_stream_end != NULL) {
|
||||
retval = parser->on_stream_end(parser);
|
||||
if (retval != FLV_OK) {
|
||||
flv_close(parser->stream);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
flv_close(parser->stream);
|
||||
return FLV_OK;
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
$Id: flv.h 231 2011-06-27 13:46:19Z marc.noirot $
|
||||
|
||||
FLV Metadata updater
|
||||
|
||||
Copyright (C) 2007-2012 Marc Noirot <marc.noirot AT gmail.com>
|
||||
|
||||
This file is part of FLVMeta.
|
||||
|
||||
FLVMeta is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FLVMeta is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FLVMeta; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#ifndef __FLV_H__
|
||||
#define __FLV_H__
|
||||
|
||||
/* Configuration of the sources */
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include "types.h"
|
||||
#include "amf.h"
|
||||
|
||||
/* error statuses */
|
||||
#define FLV_OK 0
|
||||
#define FLV_ERROR_OPEN_READ 1
|
||||
#define FLV_ERROR_NO_FLV 2
|
||||
#define FLV_ERROR_EOF 3
|
||||
#define FLV_ERROR_MEMORY 4
|
||||
#define FLV_ERROR_EMPTY_TAG 5
|
||||
#define FLV_ERROR_INVALID_METADATA_NAME 6
|
||||
#define FLV_ERROR_INVALID_METADATA 7
|
||||
|
||||
/* flv file format structure and definitions */
|
||||
|
||||
/* FLV file header */
|
||||
#define FLV_SIGNATURE "FLV"
|
||||
#define FLV_VERSION ((uint8)0x01)
|
||||
|
||||
#define FLV_FLAG_VIDEO ((uint8)0x01)
|
||||
#define FLV_FLAG_AUDIO ((uint8)0x04)
|
||||
|
||||
typedef struct __flv_header {
|
||||
byte signature[3]; /* always "FLV" */
|
||||
uint8 version; /* should be 1 */
|
||||
uint8_bitmask flags;
|
||||
uint32_be offset; /* always 9 */
|
||||
} flv_header;
|
||||
|
||||
#define FLV_HEADER_SIZE 9
|
||||
|
||||
#define flv_header_has_video(header) ((header).flags & FLV_FLAG_VIDEO)
|
||||
#define flv_header_has_audio(header) ((header).flags & FLV_FLAG_AUDIO)
|
||||
#define flv_header_get_offset(header) (swap_uint32((header).offset))
|
||||
|
||||
/* FLV tag */
|
||||
#define FLV_TAG_TYPE_AUDIO ((uint8)0x08)
|
||||
#define FLV_TAG_TYPE_VIDEO ((uint8)0x09)
|
||||
#define FLV_TAG_TYPE_META ((uint8)0x12)
|
||||
|
||||
typedef struct __flv_tag {
|
||||
uint8 type;
|
||||
uint24_be body_length; /* in bytes, total tag size minus 11 */
|
||||
uint24_be timestamp; /* milli-seconds */
|
||||
uint8 timestamp_extended; /* timestamp extension */
|
||||
uint24_be stream_id; /* reserved, must be "\0\0\0" */
|
||||
/* body comes next */
|
||||
} flv_tag;
|
||||
|
||||
#define FLV_TAG_SIZE 11
|
||||
|
||||
#define flv_tag_get_body_length(tag) (uint24_be_to_uint32((tag).body_length))
|
||||
#define flv_tag_get_timestamp(tag) \
|
||||
(uint24_be_to_uint32((tag).timestamp) + ((tag).timestamp_extended << 24))
|
||||
#define flv_tag_get_stream_id(tag) (uint24_be_to_uint32((tag).stream_id))
|
||||
|
||||
/* audio tag */
|
||||
#define FLV_AUDIO_TAG_SOUND_TYPE_MONO 0
|
||||
#define FLV_AUDIO_TAG_SOUND_TYPE_STEREO 1
|
||||
|
||||
#define FLV_AUDIO_TAG_SOUND_SIZE_8 0
|
||||
#define FLV_AUDIO_TAG_SOUND_SIZE_16 1
|
||||
|
||||
#define FLV_AUDIO_TAG_SOUND_RATE_5_5 0
|
||||
#define FLV_AUDIO_TAG_SOUND_RATE_11 1
|
||||
#define FLV_AUDIO_TAG_SOUND_RATE_22 2
|
||||
#define FLV_AUDIO_TAG_SOUND_RATE_44 3
|
||||
|
||||
#define FLV_AUDIO_TAG_SOUND_FORMAT_LINEAR_PCM 0
|
||||
#define FLV_AUDIO_TAG_SOUND_FORMAT_ADPCM 1
|
||||
#define FLV_AUDIO_TAG_SOUND_FORMAT_MP3 2
|
||||
#define FLV_AUDIO_TAG_SOUND_FORMAT_LINEAR_PCM_LE 3
|
||||
#define FLV_AUDIO_TAG_SOUND_FORMAT_NELLYMOSER_16_MONO 4
|
||||
#define FLV_AUDIO_TAG_SOUND_FORMAT_NELLYMOSER_8_MONO 5
|
||||
#define FLV_AUDIO_TAG_SOUND_FORMAT_NELLYMOSER 6
|
||||
#define FLV_AUDIO_TAG_SOUND_FORMAT_G711_A 7
|
||||
#define FLV_AUDIO_TAG_SOUND_FORMAT_G711_MU 8
|
||||
#define FLV_AUDIO_TAG_SOUND_FORMAT_RESERVED 9
|
||||
#define FLV_AUDIO_TAG_SOUND_FORMAT_AAC 10
|
||||
#define FLV_AUDIO_TAG_SOUND_FORMAT_SPEEX 11
|
||||
#define FLV_AUDIO_TAG_SOUND_FORMAT_MP3_8 14
|
||||
#define FLV_AUDIO_TAG_SOUND_FORMAT_DEVICE_SPECIFIC 15
|
||||
|
||||
typedef byte flv_audio_tag;
|
||||
|
||||
#define flv_audio_tag_sound_type(tag) (((tag) & 0x01) >> 0)
|
||||
#define flv_audio_tag_sound_size(tag) (((tag) & 0x02) >> 1)
|
||||
#define flv_audio_tag_sound_rate(tag) (((tag) & 0x0C) >> 2)
|
||||
#define flv_audio_tag_sound_format(tag) (((tag) & 0xF0) >> 4)
|
||||
|
||||
/* video tag */
|
||||
#define FLV_VIDEO_TAG_CODEC_JPEG 1
|
||||
#define FLV_VIDEO_TAG_CODEC_SORENSEN_H263 2
|
||||
#define FLV_VIDEO_TAG_CODEC_SCREEN_VIDEO 3
|
||||
#define FLV_VIDEO_TAG_CODEC_ON2_VP6 4
|
||||
#define FLV_VIDEO_TAG_CODEC_ON2_VP6_ALPHA 5
|
||||
#define FLV_VIDEO_TAG_CODEC_SCREEN_VIDEO_V2 6
|
||||
#define FLV_VIDEO_TAG_CODEC_AVC 7
|
||||
|
||||
#define FLV_VIDEO_TAG_FRAME_TYPE_KEYFRAME 1
|
||||
#define FLV_VIDEO_TAG_FRAME_TYPE_INTERFRAME 2
|
||||
#define FLV_VIDEO_TAG_FRAME_TYPE_DISPOSABLE_INTERFRAME 3
|
||||
#define FLV_VIDEO_TAG_FRAME_TYPE_GENERATED_KEYFRAME 4
|
||||
#define FLV_VIDEO_TAG_FRAME_TYPE_COMMAND_FRAME 5
|
||||
|
||||
typedef byte flv_video_tag;
|
||||
|
||||
#define flv_video_tag_codec_id(tag) (((tag) & 0x0F) >> 0)
|
||||
#define flv_video_tag_frame_type(tag) (((tag) & 0xF0) >> 4)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/* FLV helper functions */
|
||||
void flv_tag_set_timestamp(flv_tag * tag, uint32 timestamp);
|
||||
|
||||
/* FLV stream */
|
||||
#define FLV_STREAM_STATE_START 0
|
||||
#define FLV_STREAM_STATE_TAG 1
|
||||
#define FLV_STREAM_STATE_TAG_BODY 2
|
||||
#define FLV_STREAM_STATE_PREV_TAG_SIZE 3
|
||||
|
||||
typedef struct __flv_stream {
|
||||
FILE * flvin;
|
||||
uint8 state;
|
||||
flv_tag current_tag;
|
||||
file_offset_t current_tag_offset;
|
||||
uint32 current_tag_body_length;
|
||||
uint32 current_tag_body_overflow;
|
||||
} flv_stream;
|
||||
|
||||
/* FLV stream functions */
|
||||
flv_stream * flv_open(const char * file);
|
||||
int flv_read_header(flv_stream * stream, flv_header * header);
|
||||
int flv_read_prev_tag_size(flv_stream * stream, uint32 * prev_tag_size);
|
||||
int flv_read_tag(flv_stream * stream, flv_tag * tag);
|
||||
int flv_read_audio_tag(flv_stream * stream, flv_audio_tag * tag);
|
||||
int flv_read_video_tag(flv_stream * stream, flv_video_tag * tag);
|
||||
int flv_read_metadata(flv_stream * stream, amf_data ** name, amf_data ** data);
|
||||
size_t flv_read_tag_body(flv_stream * stream, void * buffer, size_t buffer_size);
|
||||
file_offset_t flv_get_current_tag_offset(flv_stream * stream);
|
||||
file_offset_t flv_get_offset(flv_stream * stream);
|
||||
void flv_reset(flv_stream * stream);
|
||||
void flv_close(flv_stream * stream);
|
||||
|
||||
/* FLV stdio writing helper functions */
|
||||
size_t flv_write_header(FILE * out, const flv_header * header);
|
||||
size_t flv_write_tag(FILE * out, const flv_tag * tag);
|
||||
|
||||
/* FLV event based parser */
|
||||
typedef struct __flv_parser {
|
||||
flv_stream * stream;
|
||||
void * user_data;
|
||||
int (* on_header)(flv_header * header, struct __flv_parser * parser);
|
||||
int (* on_tag)(flv_tag * tag, struct __flv_parser * parser);
|
||||
int (* on_metadata_tag)(flv_tag * tag, amf_data * name, amf_data * data, struct __flv_parser * parser);
|
||||
int (* on_audio_tag)(flv_tag * tag, flv_audio_tag audio_tag, struct __flv_parser * parser);
|
||||
int (* on_video_tag)(flv_tag * tag, flv_video_tag audio_tag, struct __flv_parser * parser);
|
||||
int (* on_unknown_tag)(flv_tag * tag, struct __flv_parser * parser);
|
||||
int (* on_prev_tag_size)(uint32 size, struct __flv_parser * parser);
|
||||
int (* on_stream_end)(struct __flv_parser * parser);
|
||||
} flv_parser;
|
||||
|
||||
int flv_parse(const char * file, flv_parser * parser);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* __FLV_H__ */
|
||||
@@ -1,626 +0,0 @@
|
||||
/*
|
||||
$Id: info.c 231 2011-06-27 13:46:19Z marc.noirot $
|
||||
|
||||
FLV Metadata updater
|
||||
|
||||
Copyright (C) 2007-2012 Marc Noirot <marc.noirot AT gmail.com>
|
||||
|
||||
This file is part of FLVMeta.
|
||||
|
||||
FLVMeta is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FLVMeta is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FLVMeta; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include "info.h"
|
||||
#include "avc.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#pragma warning(disable:4244)
|
||||
/*
|
||||
compute Sorensen H.263 video size
|
||||
*/
|
||||
static int compute_h263_size(flv_stream * flv_in, flv_info * info, uint32 body_length) {
|
||||
byte header[9];
|
||||
uint24_be psc_be;
|
||||
uint32 psc;
|
||||
|
||||
/* make sure we have enough bytes to read in the current tag */
|
||||
if (body_length >= 9) {
|
||||
if (flv_read_tag_body(flv_in, header, 9) < 9) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
psc_be.b[0] = header[0];
|
||||
psc_be.b[1] = header[1];
|
||||
psc_be.b[2] = header[2];
|
||||
psc = uint24_be_to_uint32(psc_be) >> 7;
|
||||
if (psc == 1) {
|
||||
uint32 psize = ((header[3] & 0x03) << 1) + ((header[4] >> 7) & 0x01);
|
||||
switch (psize) {
|
||||
case 0:
|
||||
info->video_width = ((header[4] & 0x7f) << 1) + ((header[5] >> 7) & 0x01);
|
||||
info->video_height = ((header[5] & 0x7f) << 1) + ((header[6] >> 7) & 0x01);
|
||||
break;
|
||||
case 1:
|
||||
info->video_width = ((header[4] & 0x7f) << 9) + (header[5] << 1) + ((header[6] >> 7) & 0x01);
|
||||
info->video_height = ((header[6] & 0x7f) << 9) + (header[7] << 1) + ((header[8] >> 7) & 0x01);
|
||||
break;
|
||||
case 2:
|
||||
info->video_width = 352;
|
||||
info->video_height = 288;
|
||||
break;
|
||||
case 3:
|
||||
info->video_width = 176;
|
||||
info->video_height = 144;
|
||||
break;
|
||||
case 4:
|
||||
info->video_width = 128;
|
||||
info->video_height = 96;
|
||||
break;
|
||||
case 5:
|
||||
info->video_width = 320;
|
||||
info->video_height = 240;
|
||||
break;
|
||||
case 6:
|
||||
info->video_width = 160;
|
||||
info->video_height = 120;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return FLV_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
compute Screen video size
|
||||
*/
|
||||
static int compute_screen_size(flv_stream * flv_in, flv_info * info, uint32 body_length) {
|
||||
byte header[4];
|
||||
|
||||
/* make sure we have enough bytes to read in the current tag */
|
||||
if (body_length >= 4) {
|
||||
if (flv_read_tag_body(flv_in, header, 4) < 4) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
info->video_width = ((header[0] & 0x0f) << 8) + header[1];
|
||||
info->video_height = ((header[2] & 0x0f) << 8) + header[3];
|
||||
}
|
||||
return FLV_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
compute On2 VP6 video size
|
||||
*/
|
||||
static int compute_vp6_size(flv_stream * flv_in, flv_info * info, uint32 body_length) {
|
||||
byte header[7], offset;
|
||||
|
||||
/* make sure we have enough bytes to read in the current tag */
|
||||
if (body_length >= 7) {
|
||||
if (flv_read_tag_body(flv_in, header, 7) < 7) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
/* two bytes offset if VP6 0 */
|
||||
offset = (header[1] & 0x01 || !(header[2] & 0x06)) << 1;
|
||||
info->video_width = (header[4 + offset] << 4) - (header[0] >> 4);
|
||||
info->video_height = (header[3 + offset] << 4) - (header[0] & 0x0f);
|
||||
|
||||
}
|
||||
return FLV_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
compute On2 VP6 with Alpha video size
|
||||
*/
|
||||
static int compute_vp6_alpha_size(flv_stream * flv_in, flv_info * info, uint32 body_length) {
|
||||
byte header[10], offset;
|
||||
|
||||
/* make sure we have enough bytes to read in the current tag */
|
||||
if (body_length >= 10) {
|
||||
if (flv_read_tag_body(flv_in, header, 10) < 10) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
/* two bytes offset if VP6 0 */
|
||||
offset = (header[4] & 0x01 || !(header[5] & 0x06)) << 1;
|
||||
info->video_width = (header[7 + offset] << 4) - (header[0] >> 4);
|
||||
info->video_height = (header[6 + offset] << 4) - (header[0] & 0x0f);
|
||||
}
|
||||
return FLV_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
compute AVC (H.264) video size (experimental)
|
||||
*/
|
||||
static int compute_avc_size(flv_stream * flv_in, flv_info * info, uint32 body_length) {
|
||||
return read_avc_resolution(flv_in, body_length, &(info->video_width), &(info->video_height));
|
||||
}
|
||||
|
||||
/*
|
||||
compute video width and height from the first video frame
|
||||
*/
|
||||
static int compute_video_size(flv_stream * flv_in, flv_info * info, uint32 body_length) {
|
||||
switch (info->video_codec) {
|
||||
case FLV_VIDEO_TAG_CODEC_SORENSEN_H263:
|
||||
return compute_h263_size(flv_in, info, body_length);
|
||||
case FLV_VIDEO_TAG_CODEC_SCREEN_VIDEO:
|
||||
case FLV_VIDEO_TAG_CODEC_SCREEN_VIDEO_V2:
|
||||
return compute_screen_size(flv_in, info, body_length);
|
||||
case FLV_VIDEO_TAG_CODEC_ON2_VP6:
|
||||
return compute_vp6_size(flv_in, info, body_length);
|
||||
case FLV_VIDEO_TAG_CODEC_ON2_VP6_ALPHA:
|
||||
return compute_vp6_alpha_size(flv_in, info, body_length);
|
||||
case FLV_VIDEO_TAG_CODEC_AVC:
|
||||
return compute_avc_size(flv_in, info, body_length);
|
||||
default:
|
||||
return FLV_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
read the flv file thoroughly to get all necessary information.
|
||||
|
||||
we need to check :
|
||||
- timestamp of first audio for audio delay
|
||||
- whether we have audio and video
|
||||
- first frames codecs (audio, video)
|
||||
- total audio and video data sizes
|
||||
- keyframe offsets and timestamps
|
||||
- whether the last video frame is a keyframe
|
||||
- last keyframe timestamp
|
||||
- onMetaData tag total size
|
||||
- total tags size
|
||||
- first tag after onMetaData offset
|
||||
- last timestamp
|
||||
- real video data size, number of frames, duration to compute framerate and video data rate
|
||||
- real audio data size, duration to compute audio data rate
|
||||
- video headers to find width and height. (depends on the encoding)
|
||||
*/
|
||||
int get_flv_info(flv_stream * flv_in, flv_info * info) {
|
||||
uint32 prev_timestamp_video;
|
||||
uint32 prev_timestamp_audio;
|
||||
uint32 prev_timestamp_meta;
|
||||
uint8 timestamp_extended_video;
|
||||
uint8 timestamp_extended_audio;
|
||||
uint8 timestamp_extended_meta;
|
||||
uint8 have_video_size;
|
||||
uint8 have_first_timestamp;
|
||||
uint32 tag_number;
|
||||
int result;
|
||||
flv_tag ft;
|
||||
|
||||
info->have_video = 0;
|
||||
info->have_audio = 0;
|
||||
info->video_width = 0;
|
||||
info->video_height = 0;
|
||||
info->video_codec = 0;
|
||||
info->video_frames_number = 0;
|
||||
info->audio_codec = 0;
|
||||
info->audio_size = 0;
|
||||
info->audio_rate = 0;
|
||||
info->audio_stereo = 0;
|
||||
info->video_data_size = 0;
|
||||
info->audio_data_size = 0;
|
||||
info->meta_data_size = 0;
|
||||
info->real_video_data_size = 0;
|
||||
info->real_audio_data_size = 0;
|
||||
info->video_first_timestamp = 0;
|
||||
info->audio_first_timestamp = 0;
|
||||
info->first_timestamp = 0;
|
||||
info->can_seek_to_end = 0;
|
||||
info->have_keyframes = 0;
|
||||
info->last_keyframe_timestamp = 0;
|
||||
info->on_metadata_size = 0;
|
||||
info->on_metadata_offset = 0;
|
||||
info->biggest_tag_body_size = 0;
|
||||
info->last_timestamp = 0;
|
||||
info->video_frame_duration = 0;
|
||||
info->audio_frame_duration = 0;
|
||||
info->total_prev_tags_size = 0;
|
||||
info->have_on_last_second = 0;
|
||||
info->original_on_metadata = NULL;
|
||||
info->keyframes = NULL;
|
||||
info->times = NULL;
|
||||
info->filepositions = NULL;
|
||||
|
||||
/*
|
||||
read FLV header
|
||||
*/
|
||||
|
||||
if (flv_read_header(flv_in, &(info->header)) != FLV_OK) {
|
||||
return FLV_ERROR_NO_FLV;
|
||||
}
|
||||
|
||||
info->keyframes = amf_object_new();
|
||||
info->times = amf_array_new();
|
||||
info->filepositions = amf_array_new();
|
||||
amf_object_add(info->keyframes, "times", info->times);
|
||||
amf_object_add(info->keyframes, "filepositions", info->filepositions);
|
||||
|
||||
/* first empty previous tag size */
|
||||
info->total_prev_tags_size = sizeof(uint32_be);
|
||||
|
||||
/* first timestamp */
|
||||
have_first_timestamp = 0;
|
||||
|
||||
/* extended timestamp initialization */
|
||||
prev_timestamp_video = 0;
|
||||
prev_timestamp_audio = 0;
|
||||
prev_timestamp_meta = 0;
|
||||
timestamp_extended_video = 0;
|
||||
timestamp_extended_audio = 0;
|
||||
timestamp_extended_meta = 0;
|
||||
tag_number = 0;
|
||||
have_video_size = 0;
|
||||
|
||||
while (flv_read_tag(flv_in, &ft) == FLV_OK) {
|
||||
file_offset_t offset;
|
||||
uint32 body_length;
|
||||
uint32 timestamp;
|
||||
|
||||
offset = flv_get_current_tag_offset(flv_in);
|
||||
body_length = flv_tag_get_body_length(ft);
|
||||
timestamp = flv_tag_get_timestamp(ft);
|
||||
|
||||
/* extended timestamp fixing */
|
||||
if (ft.type == FLV_TAG_TYPE_META) {
|
||||
if (timestamp < prev_timestamp_meta
|
||||
&& prev_timestamp_meta - timestamp > 0xF00000) {
|
||||
++timestamp_extended_meta;
|
||||
}
|
||||
prev_timestamp_meta = timestamp;
|
||||
if (timestamp_extended_meta > 0) {
|
||||
timestamp += timestamp_extended_meta << 24;
|
||||
}
|
||||
}
|
||||
else if (ft.type == FLV_TAG_TYPE_AUDIO) {
|
||||
if (timestamp < prev_timestamp_audio
|
||||
&& prev_timestamp_audio - timestamp > 0xF00000) {
|
||||
++timestamp_extended_audio;
|
||||
}
|
||||
prev_timestamp_audio = timestamp;
|
||||
if (timestamp_extended_audio > 0) {
|
||||
timestamp += timestamp_extended_audio << 24;
|
||||
}
|
||||
}
|
||||
else if (ft.type == FLV_TAG_TYPE_VIDEO) {
|
||||
if (timestamp < prev_timestamp_video
|
||||
&& prev_timestamp_video - timestamp > 0xF00000) {
|
||||
++timestamp_extended_video;
|
||||
}
|
||||
prev_timestamp_video = timestamp;
|
||||
if (timestamp_extended_video > 0) {
|
||||
timestamp += timestamp_extended_video << 24;
|
||||
}
|
||||
}
|
||||
|
||||
/* non-zero starting timestamp handling */
|
||||
if (!have_first_timestamp && ft.type != FLV_TAG_TYPE_META) {
|
||||
info->first_timestamp = timestamp;
|
||||
have_first_timestamp = 1;
|
||||
}
|
||||
if (timestamp > 0) {
|
||||
timestamp -= info->first_timestamp;
|
||||
}
|
||||
|
||||
/* update the info struct only if the tag is valid */
|
||||
if (ft.type == FLV_TAG_TYPE_META
|
||||
|| ft.type == FLV_TAG_TYPE_AUDIO
|
||||
|| ft.type == FLV_TAG_TYPE_VIDEO) {
|
||||
if (info->biggest_tag_body_size < body_length) {
|
||||
info->biggest_tag_body_size = body_length;
|
||||
}
|
||||
info->last_timestamp = timestamp;
|
||||
}
|
||||
|
||||
if (ft.type == FLV_TAG_TYPE_META) {
|
||||
amf_data *tag_name, *data;
|
||||
int retval;
|
||||
tag_name = data = NULL;
|
||||
|
||||
if (body_length == 0) {
|
||||
} else {
|
||||
retval = flv_read_metadata(flv_in, &tag_name, &data);
|
||||
if (retval == FLV_ERROR_EOF) {
|
||||
amf_data_free(tag_name);
|
||||
amf_data_free(data);
|
||||
return FLV_ERROR_EOF;
|
||||
} else if (retval == FLV_ERROR_INVALID_METADATA_NAME) {
|
||||
} else if (retval == FLV_ERROR_INVALID_METADATA) {
|
||||
}
|
||||
}
|
||||
|
||||
/* check metadata name */
|
||||
if (body_length > 0 && amf_data_get_type(tag_name) == AMF_TYPE_STRING) {
|
||||
char * name = (char *)amf_string_get_bytes(tag_name);
|
||||
size_t len = (size_t)amf_string_get_size(tag_name);
|
||||
|
||||
/* get info only on the first onMetaData we read */
|
||||
if (info->on_metadata_size == 0 && !strncmp(name, "onMetaData", len)) {
|
||||
info->on_metadata_size = body_length + FLV_TAG_SIZE + sizeof(uint32_be);
|
||||
info->on_metadata_offset = offset;
|
||||
|
||||
amf_data_free(data);
|
||||
}
|
||||
else {
|
||||
if (!strncmp(name, "onLastSecond", len)) {
|
||||
info->have_on_last_second = 1;
|
||||
}
|
||||
info->meta_data_size += (body_length + FLV_TAG_SIZE);
|
||||
info->total_prev_tags_size += sizeof(uint32_be);
|
||||
if (data != NULL) {
|
||||
amf_data_free(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* just ignore metadata that don't have a proper name */
|
||||
else {
|
||||
info->meta_data_size += (body_length + FLV_TAG_SIZE);
|
||||
info->total_prev_tags_size += sizeof(uint32_be);
|
||||
amf_data_free(data);
|
||||
}
|
||||
amf_data_free(tag_name);
|
||||
}
|
||||
else if (ft.type == FLV_TAG_TYPE_VIDEO) {
|
||||
flv_video_tag vt;
|
||||
|
||||
/* do not take video frame into account if body length is zero and we ignore errors */
|
||||
if (body_length == 0) {
|
||||
} else {
|
||||
if (flv_read_video_tag(flv_in, &vt) != FLV_OK) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
if (info->have_video != 1) {
|
||||
info->have_video = 1;
|
||||
info->video_codec = flv_video_tag_codec_id(vt);
|
||||
info->video_first_timestamp = timestamp;
|
||||
}
|
||||
|
||||
if (have_video_size != 1
|
||||
&& flv_video_tag_frame_type(vt) == FLV_VIDEO_TAG_FRAME_TYPE_KEYFRAME) {
|
||||
/* read first video frame to get critical info */
|
||||
result = compute_video_size(flv_in, info, body_length - sizeof(flv_video_tag));
|
||||
if (result != FLV_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (info->video_width > 0 && info->video_height > 0) {
|
||||
have_video_size = 1;
|
||||
}
|
||||
/* if we cannot fetch that information from the first tag, we'll try
|
||||
for each following video key frame */
|
||||
}
|
||||
|
||||
/* add keyframe to list */
|
||||
if (flv_video_tag_frame_type(vt) == FLV_VIDEO_TAG_FRAME_TYPE_KEYFRAME) {
|
||||
/* do not add keyframe if the previous one has the same timestamp */
|
||||
if (!info->have_keyframes
|
||||
|| (info->have_keyframes && info->last_keyframe_timestamp != timestamp)) {
|
||||
info->have_keyframes = 1;
|
||||
info->last_keyframe_timestamp = timestamp;
|
||||
amf_array_push(info->times, amf_number_new(timestamp / 1000.0));
|
||||
amf_array_push(info->filepositions, amf_number_new((number64)offset));
|
||||
}
|
||||
/* is last frame a key frame ? if so, we can seek to end */
|
||||
info->can_seek_to_end = 1;
|
||||
}
|
||||
else {
|
||||
info->can_seek_to_end = 0;
|
||||
}
|
||||
|
||||
info->real_video_data_size += (body_length - 1);
|
||||
}
|
||||
|
||||
info->video_frames_number++;
|
||||
|
||||
/*
|
||||
we assume all video frames have the same size as the first one:
|
||||
probably bogus but only used in case there's no audio in the file
|
||||
*/
|
||||
if (info->video_frame_duration == 0) {
|
||||
info->video_frame_duration = timestamp - info->video_first_timestamp;
|
||||
}
|
||||
|
||||
info->video_data_size += (body_length + FLV_TAG_SIZE);
|
||||
info->total_prev_tags_size += sizeof(uint32_be);
|
||||
}
|
||||
else if (ft.type == FLV_TAG_TYPE_AUDIO) {
|
||||
flv_audio_tag at;
|
||||
|
||||
/* do not take audio frame into account if body length is zero and we ignore errors */
|
||||
if (body_length == 0) {
|
||||
} else {
|
||||
if (flv_read_audio_tag(flv_in, &at) != FLV_OK) {
|
||||
return FLV_ERROR_EOF;
|
||||
}
|
||||
|
||||
if (info->have_audio != 1) {
|
||||
info->have_audio = 1;
|
||||
info->audio_codec = flv_audio_tag_sound_format(at);
|
||||
info->audio_rate = flv_audio_tag_sound_rate(at);
|
||||
info->audio_size = flv_audio_tag_sound_size(at);
|
||||
info->audio_stereo = flv_audio_tag_sound_type(at);
|
||||
info->audio_first_timestamp = timestamp;
|
||||
}
|
||||
/* we assume all audio frames have the same size as the first one */
|
||||
if (info->audio_frame_duration == 0) {
|
||||
info->audio_frame_duration = timestamp - info->audio_first_timestamp;
|
||||
}
|
||||
|
||||
info->real_audio_data_size += (body_length - 1);
|
||||
}
|
||||
|
||||
info->audio_data_size += (body_length + FLV_TAG_SIZE);
|
||||
info->total_prev_tags_size += sizeof(uint32_be);
|
||||
}
|
||||
else {
|
||||
return 7;
|
||||
}
|
||||
++tag_number;
|
||||
}
|
||||
|
||||
return FLV_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
compute the metadata
|
||||
*/
|
||||
void compute_metadata(flv_info * info, flv_metadata * meta) {
|
||||
uint32 new_on_metadata_size, on_last_second_size;
|
||||
file_offset_t data_size, total_filesize;
|
||||
number64 duration, video_data_rate, framerate;
|
||||
amf_data * amf_total_filesize;
|
||||
amf_data * amf_total_data_size;
|
||||
amf_node * node_t;
|
||||
amf_node * node_f;
|
||||
|
||||
meta->on_last_second_name = amf_str("onLastSecond");
|
||||
meta->on_last_second = amf_associative_array_new();
|
||||
meta->on_metadata_name = amf_str("onMetaData");
|
||||
meta->on_metadata = amf_associative_array_new();
|
||||
|
||||
amf_associative_array_add(meta->on_metadata, "hasMetadata", amf_boolean_new(1));
|
||||
amf_associative_array_add(meta->on_metadata, "hasVideo", amf_boolean_new(info->have_video));
|
||||
amf_associative_array_add(meta->on_metadata, "hasAudio", amf_boolean_new(info->have_audio));
|
||||
|
||||
if (info->have_audio) {
|
||||
duration = (info->last_timestamp - info->first_timestamp + info->audio_frame_duration) / 1000.0;
|
||||
}
|
||||
else {
|
||||
duration = (info->last_timestamp - info->first_timestamp + info->video_frame_duration) / 1000.0;
|
||||
}
|
||||
amf_associative_array_add(meta->on_metadata, "duration", amf_number_new(duration));
|
||||
|
||||
amf_associative_array_add(meta->on_metadata, "lasttimestamp", amf_number_new(info->last_timestamp / 1000.0));
|
||||
amf_associative_array_add(meta->on_metadata, "lastkeyframetimestamp", amf_number_new(info->last_keyframe_timestamp / 1000.0));
|
||||
|
||||
if (info->video_width > 0)
|
||||
amf_associative_array_add(meta->on_metadata, "width", amf_number_new(info->video_width));
|
||||
if (info->video_height > 0)
|
||||
amf_associative_array_add(meta->on_metadata, "height", amf_number_new(info->video_height));
|
||||
|
||||
video_data_rate = ((info->real_video_data_size / 1024.0) * 8.0) / duration;
|
||||
amf_associative_array_add(meta->on_metadata, "videodatarate", amf_number_new(video_data_rate));
|
||||
|
||||
framerate = info->video_frames_number / duration;
|
||||
amf_associative_array_add(meta->on_metadata, "framerate", amf_number_new(framerate));
|
||||
|
||||
if (info->have_audio) {
|
||||
number64 audio_khz, audio_sample_rate;
|
||||
number64 audio_data_rate = ((info->real_audio_data_size / 1024.0) * 8.0) / duration;
|
||||
amf_associative_array_add(meta->on_metadata, "audiodatarate", amf_number_new(audio_data_rate));
|
||||
|
||||
audio_khz = 0.0;
|
||||
switch (info->audio_rate) {
|
||||
case FLV_AUDIO_TAG_SOUND_RATE_5_5: audio_khz = 5500.0; break;
|
||||
case FLV_AUDIO_TAG_SOUND_RATE_11: audio_khz = 11000.0; break;
|
||||
case FLV_AUDIO_TAG_SOUND_RATE_22: audio_khz = 22050.0; break;
|
||||
case FLV_AUDIO_TAG_SOUND_RATE_44: audio_khz = 44100.0; break;
|
||||
}
|
||||
amf_associative_array_add(meta->on_metadata, "audiosamplerate", amf_number_new(audio_khz));
|
||||
audio_sample_rate = 0.0;
|
||||
switch (info->audio_size) {
|
||||
case FLV_AUDIO_TAG_SOUND_SIZE_8: audio_sample_rate = 8.0; break;
|
||||
case FLV_AUDIO_TAG_SOUND_SIZE_16: audio_sample_rate = 16.0; break;
|
||||
}
|
||||
amf_associative_array_add(meta->on_metadata, "audiosamplesize", amf_number_new(audio_sample_rate));
|
||||
amf_associative_array_add(meta->on_metadata, "stereo", amf_boolean_new(info->audio_stereo == FLV_AUDIO_TAG_SOUND_TYPE_STEREO));
|
||||
}
|
||||
|
||||
/* to be computed later */
|
||||
amf_total_filesize = amf_number_new(0);
|
||||
amf_associative_array_add(meta->on_metadata, "filesize", amf_total_filesize);
|
||||
|
||||
if (info->have_video) {
|
||||
amf_associative_array_add(meta->on_metadata, "videosize", amf_number_new((number64)info->video_data_size));
|
||||
}
|
||||
if (info->have_audio) {
|
||||
amf_associative_array_add(meta->on_metadata, "audiosize", amf_number_new((number64)info->audio_data_size));
|
||||
}
|
||||
|
||||
/* to be computed later */
|
||||
amf_total_data_size = amf_number_new(0);
|
||||
amf_associative_array_add(meta->on_metadata, "datasize", amf_total_data_size);
|
||||
|
||||
amf_associative_array_add(meta->on_metadata, "metadatacreator", amf_str("xingmeng"));
|
||||
|
||||
amf_associative_array_add(meta->on_metadata, "metadatadate", amf_date_new((number64)time(NULL)*1000, 0));
|
||||
if (info->have_audio) {
|
||||
amf_associative_array_add(meta->on_metadata, "audiocodecid", amf_number_new((number64)info->audio_codec));
|
||||
}
|
||||
if (info->have_video) {
|
||||
amf_associative_array_add(meta->on_metadata, "videocodecid", amf_number_new((number64)info->video_codec));
|
||||
}
|
||||
if (info->have_audio && info->have_video) {
|
||||
number64 audio_delay = ((sint32)info->audio_first_timestamp - (sint32)info->video_first_timestamp) / 1000.0;
|
||||
amf_associative_array_add(meta->on_metadata, "audiodelay", amf_number_new((number64)audio_delay));
|
||||
}
|
||||
amf_associative_array_add(meta->on_metadata, "canSeekToEnd", amf_boolean_new(info->can_seek_to_end));
|
||||
|
||||
/* only add empty cuepoints if we don't preserve existing tags OR if the existing tags don't have cuepoints */
|
||||
if ((amf_associative_array_get(info->original_on_metadata, "cuePoints") == NULL)) {
|
||||
amf_associative_array_add(meta->on_metadata, "hasCuePoints", amf_boolean_new(0));
|
||||
amf_associative_array_add(meta->on_metadata, "cuePoints", amf_array_new());
|
||||
}
|
||||
amf_associative_array_add(meta->on_metadata, "hasKeyframes", amf_boolean_new(info->have_keyframes));
|
||||
amf_associative_array_add(meta->on_metadata, "keyframes", info->keyframes);
|
||||
|
||||
/*
|
||||
When we know the final size, we can recompute te offsets for the filepositions, and the final datasize.
|
||||
*/
|
||||
new_on_metadata_size = FLV_TAG_SIZE + sizeof(uint32_be) +
|
||||
(uint32)(amf_data_size(meta->on_metadata_name) + amf_data_size(meta->on_metadata));
|
||||
on_last_second_size = (uint32)(amf_data_size(meta->on_last_second_name) + amf_data_size(meta->on_last_second));
|
||||
|
||||
node_t = amf_array_first(info->times);
|
||||
node_f = amf_array_first(info->filepositions);
|
||||
while (node_t != NULL || node_f != NULL) {
|
||||
amf_data * amf_filepos = amf_array_get(node_f);
|
||||
number64 offset = amf_number_get_value(amf_filepos) + new_on_metadata_size - info->on_metadata_size;
|
||||
number64 timestamp = amf_number_get_value(amf_array_get(node_t));
|
||||
|
||||
/* after the onLastSecond event we need to take in account the tag size */
|
||||
if (!info->have_on_last_second && (info->last_timestamp - timestamp * 1000) <= 1000) {
|
||||
offset += (FLV_TAG_SIZE + on_last_second_size + sizeof(uint32_be));
|
||||
}
|
||||
|
||||
amf_number_set_value(amf_filepos, offset);
|
||||
node_t = amf_array_next(node_t);
|
||||
node_f = amf_array_next(node_f);
|
||||
}
|
||||
|
||||
/* compute data size, ie. size of metadata excluding prev_tag_size */
|
||||
data_size = info->meta_data_size + FLV_TAG_SIZE +
|
||||
(uint32)(amf_data_size(meta->on_metadata_name) + amf_data_size(meta->on_metadata));
|
||||
if (!info->have_on_last_second) {
|
||||
data_size += (uint32)on_last_second_size + FLV_TAG_SIZE;
|
||||
}
|
||||
amf_number_set_value(amf_total_data_size, (number64)data_size);
|
||||
|
||||
/* compute total file size */
|
||||
total_filesize = FLV_HEADER_SIZE + info->total_prev_tags_size + info->video_data_size +
|
||||
info->audio_data_size + info->meta_data_size + new_on_metadata_size;
|
||||
|
||||
if (!info->have_on_last_second) {
|
||||
/* if we have to add onLastSecond, we must count the header and new prevTagSize we add */
|
||||
total_filesize += (uint32)(FLV_TAG_SIZE + on_last_second_size + sizeof(uint32_be));
|
||||
}
|
||||
|
||||
amf_number_set_value(amf_total_filesize, (number64)total_filesize);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
$Id: info.h 231 2011-06-27 13:46:19Z marc.noirot $
|
||||
|
||||
FLV Metadata updater
|
||||
|
||||
Copyright (C) 2007-2012 Marc Noirot <marc.noirot AT gmail.com>
|
||||
|
||||
This file is part of FLVMeta.
|
||||
|
||||
FLVMeta is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FLVMeta is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FLVMeta; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#ifndef __INFO_H__
|
||||
#define __INFO_H__
|
||||
#include "flv.h"
|
||||
|
||||
typedef struct __flv_info {
|
||||
flv_header header;
|
||||
uint8 have_video;
|
||||
uint8 have_audio;
|
||||
uint32 video_width;
|
||||
uint32 video_height;
|
||||
uint8 video_codec;
|
||||
uint32 video_frames_number;
|
||||
uint8 audio_codec;
|
||||
uint8 audio_size;
|
||||
uint8 audio_rate;
|
||||
uint8 audio_stereo;
|
||||
file_offset_t video_data_size;
|
||||
file_offset_t audio_data_size;
|
||||
file_offset_t meta_data_size;
|
||||
file_offset_t real_video_data_size;
|
||||
file_offset_t real_audio_data_size;
|
||||
uint32 video_first_timestamp;
|
||||
uint32 audio_first_timestamp;
|
||||
uint32 first_timestamp;
|
||||
uint8 can_seek_to_end;
|
||||
uint8 have_keyframes;
|
||||
uint32 last_keyframe_timestamp;
|
||||
uint32 on_metadata_size;
|
||||
file_offset_t on_metadata_offset;
|
||||
uint32 biggest_tag_body_size;
|
||||
uint32 last_timestamp;
|
||||
uint32 video_frame_duration;
|
||||
uint32 audio_frame_duration;
|
||||
file_offset_t total_prev_tags_size;
|
||||
uint8 have_on_last_second;
|
||||
amf_data * original_on_metadata;
|
||||
amf_data * keyframes;
|
||||
amf_data * times;
|
||||
amf_data * filepositions;
|
||||
} flv_info;
|
||||
|
||||
typedef struct __flv_metadata {
|
||||
amf_data * on_last_second_name;
|
||||
amf_data * on_last_second;
|
||||
amf_data * on_metadata_name;
|
||||
amf_data * on_metadata;
|
||||
} flv_metadata;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
int get_flv_info(flv_stream * flv_in, flv_info * info);
|
||||
|
||||
void compute_metadata(flv_info * info, flv_metadata * meta);
|
||||
|
||||
void compute_current_metadata(flv_info * info, flv_metadata * meta);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* __INFO_H__ */
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
$Id: types.c 231 2011-06-27 13:46:19Z marc.noirot $
|
||||
|
||||
FLV Metadata updater
|
||||
|
||||
Copyright (C) 2007-2012 Marc Noirot <marc.noirot AT gmail.com>
|
||||
|
||||
This file is part of FLVMeta.
|
||||
|
||||
FLVMeta is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FLVMeta is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FLVMeta; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include "types.h"
|
||||
|
||||
#ifndef WORDS_BIGENDIAN
|
||||
|
||||
/* swap 64 bits doubles */
|
||||
typedef union __convert_u {
|
||||
uint64 i;
|
||||
number64 f;
|
||||
} convert_u;
|
||||
|
||||
number64 swap_number64(number64 n) {
|
||||
convert_u c;
|
||||
c.f = n;
|
||||
c.i = (((c.i & 0x00000000000000FFULL) << 56) |
|
||||
((c.i & 0x000000000000FF00ULL) << 40) |
|
||||
((c.i & 0x0000000000FF0000ULL) << 24) |
|
||||
((c.i & 0x00000000FF000000ULL) << 8) |
|
||||
((c.i & 0x000000FF00000000ULL) >> 8) |
|
||||
((c.i & 0x0000FF0000000000ULL) >> 24) |
|
||||
((c.i & 0x00FF000000000000ULL) >> 40) |
|
||||
((c.i & 0xFF00000000000000ULL) >> 56));
|
||||
return c.f;
|
||||
}
|
||||
#endif /* !defined WORDS_BIGENDIAN */
|
||||
|
||||
/* convert native integers into 24 bits big endian integers */
|
||||
uint24_be uint32_to_uint24_be(uint32 l) {
|
||||
uint24_be r;
|
||||
r.b[0] = (uint8)((l & 0x00FF0000U) >> 16);
|
||||
r.b[1] = (uint8)((l & 0x0000FF00U) >> 8);
|
||||
r.b[2] = (uint8) (l & 0x000000FFU);
|
||||
return r;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
/*
|
||||
These functions assume fpos_t is a 64-bit signed integer
|
||||
*/
|
||||
|
||||
file_offset_t lfs_ftell(FILE * stream) {
|
||||
fpos_t p;
|
||||
if (fgetpos(stream, &p) == 0) {
|
||||
return (file_offset_t)p;
|
||||
}
|
||||
else {
|
||||
return -1LL;
|
||||
}
|
||||
}
|
||||
|
||||
int lfs_fseek(FILE * stream, file_offset_t offset, int whence) {
|
||||
fpos_t p;
|
||||
if (fgetpos(stream, &p) == 0) {
|
||||
switch (whence) {
|
||||
case SEEK_CUR: p += offset; break;
|
||||
case SEEK_SET: p = offset; break;
|
||||
/*case SEEK_END:; not implemented here */
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
fsetpos(stream, &p);
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* WIN32 */
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
$Id: types.h 231 2011-06-27 13:46:19Z marc.noirot $
|
||||
|
||||
FLV Metadata updater
|
||||
|
||||
Copyright (C) 2007-2012 Marc Noirot <marc.noirot AT gmail.com>
|
||||
|
||||
This file is part of FLVMeta.
|
||||
|
||||
FLVMeta is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
FLVMeta is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with FLVMeta; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#ifndef __TYPES_H__
|
||||
#define __TYPES_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
typedef uint8_t byte, uint8, uint8_bitmask;
|
||||
|
||||
typedef uint16_t uint16, uint16_be, uint16_le;
|
||||
|
||||
typedef int16_t sint16, sint16_be, sint16_le;
|
||||
|
||||
typedef uint32_t uint32, uint32_be, uint32_le;
|
||||
|
||||
typedef int32_t sint32, sint32_be, sint32_le;
|
||||
|
||||
typedef struct __uint24 {
|
||||
uint8 b[3];
|
||||
} uint24, uint24_be, uint24_le;
|
||||
|
||||
typedef uint64_t uint64, uint64_le, uint64_be;
|
||||
|
||||
typedef int64_t sint64, sint64_le, sint64_be;
|
||||
|
||||
//typedef
|
||||
//#if SIZEOF_FLOAT == 8
|
||||
//float
|
||||
//#elif SIZEOF_DOUBLE == 8
|
||||
//double
|
||||
//#elif SIZEOF_LONG_DOUBLE == 8
|
||||
//long double
|
||||
//#else
|
||||
//uint64_t
|
||||
//#endif
|
||||
//number64, number64_le, number64_be;
|
||||
|
||||
typedef double number64, number64_le, number64_be;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
|
||||
# define swap_uint16(x) (x)
|
||||
# define swap_sint16(x) (x)
|
||||
# define swap_uint32(x) (x)
|
||||
# define swap_number64(x) (x)
|
||||
|
||||
#else /* !defined WORDS_BIGENDIAN */
|
||||
|
||||
/* swap 16 bits integers */
|
||||
# define swap_uint16(x) ((uint16)((((x) & 0x00FFU) << 8) | \
|
||||
(((x) & 0xFF00U) >> 8)))
|
||||
# define swap_sint16(x) ((sint16)((((x) & 0x00FF) << 8) | \
|
||||
(((x) & 0xFF00) >> 8)))
|
||||
|
||||
/* swap 32 bits integers */
|
||||
# define swap_uint32(x) ((uint32)((((x) & 0x000000FFU) << 24) | \
|
||||
(((x) & 0x0000FF00U) << 8) | \
|
||||
(((x) & 0x00FF0000U) >> 8) | \
|
||||
(((x) & 0xFF000000U) >> 24)))
|
||||
|
||||
/* swap 64 bits doubles */
|
||||
number64 swap_number64(number64);
|
||||
|
||||
#endif /* WORDS_BIGENDIAN */
|
||||
|
||||
/* convert big endian 24 bits integers to native integers */
|
||||
# define uint24_be_to_uint32(x) ((uint32)(((x).b[0] << 16) | \
|
||||
((x).b[1] << 8) | (x).b[2]))
|
||||
|
||||
/* convert native integers into 24 bits big endian integers */
|
||||
uint24_be uint32_to_uint24_be(uint32);
|
||||
|
||||
/* large file support */
|
||||
#ifdef HAVE_FSEEKO
|
||||
# define lfs_ftell ftello
|
||||
# define lfs_fseek fseeko
|
||||
|
||||
# define FILE_OFFSET_T_64_BITS 1
|
||||
typedef off_t file_offset_t;
|
||||
|
||||
#else /* !HAVE_SEEKO */
|
||||
|
||||
# ifdef WIN32
|
||||
|
||||
# define FILE_OFFSET_T_64_BITS 1
|
||||
typedef long long int file_offset_t;
|
||||
|
||||
/* Win32 large file support */
|
||||
file_offset_t lfs_ftell(FILE * stream);
|
||||
int lfs_fseek(FILE * stream, file_offset_t offset, int whence);
|
||||
|
||||
# else /* !defined WIN32 */
|
||||
|
||||
# define lfs_ftell ftell
|
||||
# define lfs_fseek fseek
|
||||
|
||||
typedef long file_offset_t;
|
||||
|
||||
# endif /* WIN32 */
|
||||
|
||||
#endif /* HAVE_FSEEKO */
|
||||
|
||||
/* file offset printf specifier */
|
||||
#ifdef FILE_OFFSET_T_64_BITS
|
||||
# define FILE_OFFSET_PRINTF_FORMAT "ll"
|
||||
#else
|
||||
# define FILE_OFFSET_PRINTF_FORMAT "l"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* __TYPES_H__ */
|
||||
@@ -0,0 +1,519 @@
|
||||
//
|
||||
// LFStreamRtmpSocket.m
|
||||
// LFLiveKit
|
||||
//
|
||||
// Created by admin on 16/5/18.
|
||||
// Copyright © 2016年 live Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LFStreamRtmpSocket.h"
|
||||
#import "rtmp.h"
|
||||
#import "YYDispatchQueuePool.h"
|
||||
|
||||
static const NSInteger RetryTimesBreaken = 20;///< 重连1分钟 3秒一次 一共20次
|
||||
static const NSInteger RetryTimesMargin = 3;
|
||||
|
||||
static dispatch_queue_t YYRtmpSendQueue() {
|
||||
YYDispatchQueuePool *pool = [[YYDispatchQueuePool alloc] initWithName:@"com.youku.laifeng.rtmpsendQueue" queueCount:1 qos:NSQualityOfServiceDefault];
|
||||
dispatch_queue_t queue = [pool queue];
|
||||
return queue;
|
||||
}
|
||||
#define RTMP_RECEIVE_TIMEOUT 2
|
||||
#define DATA_ITEMS_MAX_COUNT 100
|
||||
#define RTMP_DATA_RESERVE_SIZE 400
|
||||
#define RTMP_HEAD_SIZE (sizeof(RTMPPacket)+RTMP_MAX_HEADER_SIZE)
|
||||
|
||||
#define SAVC(x) static const AVal av_##x = AVC(#x)
|
||||
|
||||
static const AVal av_setDataFrame = AVC("@setDataFrame");
|
||||
static const AVal av_SDKVersion = AVC("LFLiveKit 1.7.3");
|
||||
SAVC(onMetaData);
|
||||
SAVC(duration);
|
||||
SAVC(width);
|
||||
SAVC(height);
|
||||
SAVC(videocodecid);
|
||||
SAVC(videodatarate);
|
||||
SAVC(framerate);
|
||||
SAVC(audiocodecid);
|
||||
SAVC(audiodatarate);
|
||||
SAVC(audiosamplerate);
|
||||
SAVC(audiosamplesize);
|
||||
SAVC(audiochannels);
|
||||
SAVC(stereo);
|
||||
SAVC(encoder);
|
||||
SAVC(av_stereo);
|
||||
SAVC(fileSize);
|
||||
SAVC(avc1);
|
||||
SAVC(mp4a);
|
||||
|
||||
@interface LFStreamRtmpSocket ()<LFStreamingBufferDelegate>
|
||||
{
|
||||
PILI_RTMP* _rtmp;
|
||||
}
|
||||
@property (nonatomic, weak) id<LFStreamSocketDelegate> delegate;
|
||||
@property (nonatomic, strong) LFLiveStreamInfo *stream;
|
||||
@property (nonatomic, strong) LFStreamingBuffer *buffer;
|
||||
@property (nonatomic, strong) LFLiveDebug *debugInfo;
|
||||
//错误信息
|
||||
@property (nonatomic, assign) RTMPError error;
|
||||
@property (nonatomic, assign) NSInteger retryTimes4netWorkBreaken;
|
||||
@property (nonatomic, assign) NSInteger reconnectInterval;
|
||||
@property (nonatomic, assign) NSInteger reconnectCount;
|
||||
|
||||
@property (nonatomic, assign) BOOL isSending;
|
||||
@property (nonatomic, assign) BOOL isConnected;
|
||||
@property (nonatomic, assign) BOOL isConnecting;
|
||||
@property (nonatomic, assign) BOOL isReconnecting;
|
||||
|
||||
@property (nonatomic, assign) BOOL sendVideoHead;
|
||||
@property (nonatomic, assign) BOOL sendAudioHead;
|
||||
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@implementation LFStreamRtmpSocket
|
||||
|
||||
#pragma mark -- LFStreamSocket
|
||||
- (nullable instancetype)initWithStream:(nullable LFLiveStreamInfo*)stream videoSize:(CGSize)videoSize reconnectInterval:(NSInteger)reconnectInterval reconnectCount:(NSInteger)reconnectCount{
|
||||
if(!stream) @throw [NSException exceptionWithName:@"LFStreamRtmpSocket init error" reason:@"stream is nil" userInfo:nil];
|
||||
if(self = [super init]){
|
||||
_stream = stream;
|
||||
if(reconnectInterval > 0) _reconnectInterval = reconnectInterval;
|
||||
else _reconnectInterval = RetryTimesMargin;
|
||||
|
||||
if(reconnectCount > 0) _reconnectCount = reconnectCount;
|
||||
else _reconnectCount = RetryTimesBreaken;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) start{
|
||||
dispatch_async(YYRtmpSendQueue(), ^{
|
||||
[self _start];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)_start{
|
||||
if(!_stream) return;
|
||||
if(_isConnecting) return;
|
||||
if(_rtmp != NULL) return;
|
||||
self.debugInfo.streamId = self.stream.streamId;
|
||||
self.debugInfo.uploadUrl = self.stream.url;
|
||||
self.debugInfo.isRtmp = YES;
|
||||
[self clean];
|
||||
[self RTMP264_Connect:(char*)[_stream.url cStringUsingEncoding:NSASCIIStringEncoding]];
|
||||
}
|
||||
|
||||
- (void) stop{
|
||||
dispatch_async(YYRtmpSendQueue(), ^{
|
||||
[self _stop];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)_stop{
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[self.delegate socketStatus:self status:LFLiveStop];
|
||||
}
|
||||
if(_rtmp != NULL){
|
||||
PILI_RTMP_Close(_rtmp, &_error);
|
||||
PILI_RTMP_Free(_rtmp);
|
||||
_rtmp = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (void) sendFrame:(LFFrame*)frame{
|
||||
dispatch_async(YYRtmpSendQueue(), ^{
|
||||
if(!frame) return;
|
||||
[self.buffer appendObject:frame];
|
||||
[self sendFrame];
|
||||
});
|
||||
}
|
||||
|
||||
- (void) setDelegate:(id<LFStreamSocketDelegate>)delegate{
|
||||
_delegate = delegate;
|
||||
}
|
||||
|
||||
#pragma mark -- CustomMethod
|
||||
- (void)sendFrame{
|
||||
if(!self.isSending && self.buffer.list.count > 0){
|
||||
self.isSending = YES;
|
||||
|
||||
if(!_isConnected || _isReconnecting || _isConnecting || !_rtmp) return;
|
||||
|
||||
// 调用发送接口
|
||||
LFFrame *frame = [self.buffer popFirstObject];
|
||||
if([frame isKindOfClass:[LFVideoFrame class]]){
|
||||
if(!self.sendVideoHead){
|
||||
self.sendVideoHead = YES;
|
||||
[self sendVideoHeader:(LFVideoFrame*)frame];
|
||||
}else{
|
||||
[self sendVideo:(LFVideoFrame*)frame];
|
||||
}
|
||||
}else{
|
||||
if(!self.sendAudioHead){
|
||||
self.sendAudioHead = YES;
|
||||
[self sendAudioHeader:(LFAudioFrame*)frame];
|
||||
}else{
|
||||
[self sendAudio:frame];
|
||||
}
|
||||
|
||||
}
|
||||
self.debugInfo.dataFlow += frame.data.length;
|
||||
if(CACurrentMediaTime()*1000 - self.debugInfo.timeStamp < 1000) {
|
||||
self.debugInfo.bandwidth += frame.data.length;
|
||||
if([frame isKindOfClass:[LFAudioFrame class]]){
|
||||
self.debugInfo.capturedAudioCount ++;
|
||||
}else{
|
||||
self.debugInfo.capturedVideoCount ++;
|
||||
}
|
||||
self.debugInfo.unSendCount = self.buffer.list.count;
|
||||
}else {
|
||||
self.debugInfo.currentBandwidth = self.debugInfo.bandwidth;
|
||||
self.debugInfo.currentCapturedAudioCount = self.debugInfo.capturedAudioCount;
|
||||
self.debugInfo.currentCapturedVideoCount = self.debugInfo.capturedVideoCount;
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketDebug:debugInfo:)]){
|
||||
[self.delegate socketDebug:self debugInfo:self.debugInfo];
|
||||
}
|
||||
|
||||
self.debugInfo.bandwidth = 0;
|
||||
self.debugInfo.capturedAudioCount = 0;
|
||||
self.debugInfo.capturedVideoCount = 0;
|
||||
self.debugInfo.timeStamp = CACurrentMediaTime()*1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clean{
|
||||
_isConnecting = NO;
|
||||
_isReconnecting = NO;
|
||||
_isSending = NO;
|
||||
_isConnected = NO;
|
||||
_sendAudioHead = NO;
|
||||
_sendVideoHead = NO;
|
||||
self.debugInfo = nil;
|
||||
[self.buffer removeAllObject];
|
||||
self.retryTimes4netWorkBreaken = 0;
|
||||
}
|
||||
|
||||
-(NSInteger) RTMP264_Connect:(char *)push_url{
|
||||
//由于摄像头的timestamp是一直在累加,需要每次得到相对时间戳
|
||||
//分配与初始化
|
||||
if(_isConnecting) return -1;
|
||||
|
||||
_isConnecting = YES;
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[self.delegate socketStatus:self status:LFLivePending];
|
||||
}
|
||||
|
||||
if(_rtmp != NULL){
|
||||
PILI_RTMP_Close(_rtmp, &_error);
|
||||
PILI_RTMP_Free(_rtmp);
|
||||
}
|
||||
|
||||
_rtmp = PILI_RTMP_Alloc();
|
||||
PILI_RTMP_Init(_rtmp);
|
||||
|
||||
//设置URL
|
||||
if (PILI_RTMP_SetupURL(_rtmp, push_url, &_error) == FALSE){
|
||||
//log(LOG_ERR, "RTMP_SetupURL() failed!");
|
||||
goto Failed;
|
||||
}
|
||||
|
||||
_rtmp->m_errorCallback = RTMPErrorCallback;
|
||||
_rtmp->m_connCallback = ConnectionTimeCallback;
|
||||
_rtmp->m_userData = (__bridge void*)self;
|
||||
_rtmp->m_msgCounter = 1;
|
||||
_rtmp->Link.timeout = RTMP_RECEIVE_TIMEOUT;
|
||||
//设置可写,即发布流,这个函数必须在连接前使用,否则无效
|
||||
PILI_RTMP_EnableWrite(_rtmp);
|
||||
|
||||
//连接服务器
|
||||
if (PILI_RTMP_Connect(_rtmp, NULL, &_error) == FALSE){
|
||||
goto Failed;
|
||||
}
|
||||
|
||||
//连接流
|
||||
if (PILI_RTMP_ConnectStream(_rtmp, 0, &_error) == FALSE) {
|
||||
goto Failed;
|
||||
}
|
||||
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[self.delegate socketStatus:self status:LFLiveStart];
|
||||
}
|
||||
|
||||
[self sendMetaData];
|
||||
|
||||
_isConnected = YES;
|
||||
_isConnecting = NO;
|
||||
_isReconnecting = NO;
|
||||
_isSending = NO;
|
||||
_retryTimes4netWorkBreaken = 0;
|
||||
return 0;
|
||||
|
||||
Failed:
|
||||
PILI_RTMP_Close(_rtmp, &_error);
|
||||
PILI_RTMP_Free(_rtmp);
|
||||
_rtmp = NULL;
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketDidError:errorCode:)]){
|
||||
[self.delegate socketDidError:self errorCode:LFLiveSocketError_ConnectSocket];
|
||||
}
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[self.delegate socketStatus:self status:LFLiveError];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#pragma mark -- Rtmp Send
|
||||
|
||||
- (void)sendMetaData {
|
||||
PILI_RTMPPacket packet;
|
||||
|
||||
char pbuf[2048], *pend = pbuf+sizeof(pbuf);
|
||||
|
||||
packet.m_nChannel = 0x03; // control channel (invoke)
|
||||
packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
|
||||
packet.m_packetType = RTMP_PACKET_TYPE_INFO;
|
||||
packet.m_nTimeStamp = 0;
|
||||
packet.m_nInfoField2 = _rtmp->m_stream_id;
|
||||
packet.m_hasAbsTimestamp = TRUE;
|
||||
packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
|
||||
|
||||
char *enc = packet.m_body;
|
||||
enc = AMF_EncodeString(enc, pend, &av_setDataFrame);
|
||||
enc = AMF_EncodeString(enc, pend, &av_onMetaData);
|
||||
|
||||
*enc++ = AMF_OBJECT;
|
||||
|
||||
enc = AMF_EncodeNamedNumber(enc, pend, &av_duration, 0.0);
|
||||
enc = AMF_EncodeNamedNumber(enc, pend, &av_fileSize, 0.0);
|
||||
|
||||
// videosize
|
||||
enc = AMF_EncodeNamedNumber(enc, pend, &av_width, _stream.videoConfiguration.videoSize.width);
|
||||
enc = AMF_EncodeNamedNumber(enc, pend, &av_height, _stream.videoConfiguration.videoSize.height);
|
||||
|
||||
// video
|
||||
enc = AMF_EncodeNamedString(enc, pend, &av_videocodecid, &av_avc1);
|
||||
|
||||
enc = AMF_EncodeNamedNumber(enc, pend, &av_videodatarate, _stream.videoConfiguration.videoBitRate / 1000.f);
|
||||
enc = AMF_EncodeNamedNumber(enc, pend, &av_framerate, _stream.videoConfiguration.videoFrameRate);
|
||||
|
||||
// audio
|
||||
enc = AMF_EncodeNamedString(enc, pend, &av_audiocodecid, &av_mp4a);
|
||||
enc = AMF_EncodeNamedNumber(enc, pend, &av_audiodatarate, _stream.audioConfiguration.audioBitrate);
|
||||
|
||||
enc = AMF_EncodeNamedNumber(enc, pend, &av_audiosamplerate, _stream.audioConfiguration.audioSampleRate);
|
||||
enc = AMF_EncodeNamedNumber(enc, pend, &av_audiosamplesize, 16.0);
|
||||
enc = AMF_EncodeNamedBoolean(enc, pend, &av_stereo, _stream.audioConfiguration.numberOfChannels==2);
|
||||
|
||||
// sdk version
|
||||
enc = AMF_EncodeNamedString(enc, pend, &av_encoder, &av_SDKVersion);
|
||||
|
||||
*enc++ = 0;
|
||||
*enc++ = 0;
|
||||
*enc++ = AMF_OBJECT_END;
|
||||
|
||||
packet.m_nBodySize = enc - packet.m_body;
|
||||
if(!PILI_RTMP_SendPacket(_rtmp, &packet, FALSE, &_error)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)sendVideoHeader:(LFVideoFrame*)videoFrame {
|
||||
if(!videoFrame || !videoFrame.sps || !videoFrame.pps) return;
|
||||
|
||||
unsigned char * body=NULL;
|
||||
NSInteger iIndex = 0;
|
||||
NSInteger rtmpLength = 1024;
|
||||
const char *sps = videoFrame.sps.bytes;
|
||||
const char *pps = videoFrame.pps.bytes;
|
||||
NSInteger sps_len = videoFrame.sps.length;
|
||||
NSInteger pps_len = videoFrame.pps.length;
|
||||
|
||||
body = (unsigned char*)malloc(rtmpLength);
|
||||
memset(body,0,rtmpLength);
|
||||
|
||||
body[iIndex++] = 0x17;
|
||||
body[iIndex++] = 0x00;
|
||||
|
||||
body[iIndex++] = 0x00;
|
||||
body[iIndex++] = 0x00;
|
||||
body[iIndex++] = 0x00;
|
||||
|
||||
body[iIndex++] = 0x01;
|
||||
body[iIndex++] = sps[1];
|
||||
body[iIndex++] = sps[2];
|
||||
body[iIndex++] = sps[3];
|
||||
body[iIndex++] = 0xff;
|
||||
|
||||
/*sps*/
|
||||
body[iIndex++] = 0xe1;
|
||||
body[iIndex++] = (sps_len >> 8) & 0xff;
|
||||
body[iIndex++] = sps_len & 0xff;
|
||||
memcpy(&body[iIndex],sps,sps_len);
|
||||
iIndex += sps_len;
|
||||
|
||||
/*pps*/
|
||||
body[iIndex++] = 0x01;
|
||||
body[iIndex++] = (pps_len >> 8) & 0xff;
|
||||
body[iIndex++] = (pps_len) & 0xff;
|
||||
memcpy(&body[iIndex], pps, pps_len);
|
||||
iIndex += pps_len;
|
||||
|
||||
[self sendPacket:RTMP_PACKET_TYPE_VIDEO data:body size:iIndex nTimestamp:0];
|
||||
free(body);
|
||||
}
|
||||
|
||||
|
||||
- (void)sendVideo:(LFVideoFrame*)frame{
|
||||
if(!frame || !frame.data || frame.data.length < 11) return;
|
||||
|
||||
NSInteger i = 0;
|
||||
NSInteger rtmpLength = frame.data.length+9;
|
||||
unsigned char *body = (unsigned char*)malloc(rtmpLength);
|
||||
memset(body,0,rtmpLength);
|
||||
|
||||
if(frame.isKeyFrame){
|
||||
body[i++] = 0x17;// 1:Iframe 7:AVC
|
||||
} else{
|
||||
body[i++] = 0x27;// 2:Pframe 7:AVC
|
||||
}
|
||||
body[i++] = 0x01;// AVC NALU
|
||||
body[i++] = 0x00;
|
||||
body[i++] = 0x00;
|
||||
body[i++] = 0x00;
|
||||
body[i++] = (frame.data.length >> 24) & 0xff;
|
||||
body[i++] = (frame.data.length >> 16) & 0xff;
|
||||
body[i++] = (frame.data.length >> 8) & 0xff;
|
||||
body[i++] = (frame.data.length ) & 0xff;
|
||||
memcpy(&body[i],frame.data.bytes,frame.data.length);
|
||||
|
||||
[self sendPacket:RTMP_PACKET_TYPE_VIDEO data:body size:(rtmpLength) nTimestamp:frame.timestamp];
|
||||
free(body);
|
||||
}
|
||||
|
||||
-(NSInteger) sendPacket:(unsigned int)nPacketType data:(unsigned char *)data size:(NSInteger) size nTimestamp:(uint64_t) nTimestamp{
|
||||
NSInteger rtmpLength = size;
|
||||
PILI_RTMPPacket rtmp_pack;
|
||||
PILI_RTMPPacket_Reset(&rtmp_pack);
|
||||
PILI_RTMPPacket_Alloc(&rtmp_pack,(uint32_t)rtmpLength);
|
||||
|
||||
rtmp_pack.m_nBodySize = (uint32_t)size;
|
||||
memcpy(rtmp_pack.m_body,data,size);
|
||||
rtmp_pack.m_hasAbsTimestamp = 0;
|
||||
rtmp_pack.m_packetType = nPacketType;
|
||||
if(_rtmp) rtmp_pack.m_nInfoField2 = _rtmp->m_stream_id;
|
||||
rtmp_pack.m_nChannel = 0x04;
|
||||
rtmp_pack.m_headerType = RTMP_PACKET_SIZE_LARGE;
|
||||
if (RTMP_PACKET_TYPE_AUDIO == nPacketType && size !=4){
|
||||
rtmp_pack.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
|
||||
}
|
||||
rtmp_pack.m_nTimeStamp = (uint32_t)nTimestamp;
|
||||
|
||||
NSInteger nRet = [self RtmpPacketSend:&rtmp_pack];
|
||||
|
||||
PILI_RTMPPacket_Free(&rtmp_pack);
|
||||
return nRet;
|
||||
}
|
||||
|
||||
- (NSInteger)RtmpPacketSend:(PILI_RTMPPacket*)packet{
|
||||
if (PILI_RTMP_IsConnected(_rtmp)){
|
||||
int success = PILI_RTMP_SendPacket(_rtmp,packet,0,&_error);
|
||||
if(success){
|
||||
self.isSending = NO;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self sendFrame];
|
||||
});
|
||||
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
- (void)sendAudioHeader:(LFAudioFrame*)audioFrame{
|
||||
if(!audioFrame || !audioFrame.audioInfo) return;
|
||||
|
||||
NSInteger rtmpLength = audioFrame.audioInfo.length + 2;/*spec data长度,一般是2*/
|
||||
unsigned char * body = (unsigned char*)malloc(rtmpLength);
|
||||
memset(body,0,rtmpLength);
|
||||
|
||||
/*AF 00 + AAC RAW data*/
|
||||
body[0] = 0xAF;
|
||||
body[1] = 0x00;
|
||||
memcpy(&body[2],audioFrame.audioInfo.bytes,audioFrame.audioInfo.length); /*spec_buf是AAC sequence header数据*/
|
||||
[self sendPacket:RTMP_PACKET_TYPE_AUDIO data:body size:rtmpLength nTimestamp:0];
|
||||
free(body);
|
||||
}
|
||||
|
||||
- (void)sendAudio:(LFFrame*)frame {
|
||||
if(!frame) return;
|
||||
|
||||
NSInteger rtmpLength = frame.data.length + 2;/*spec data长度,一般是2*/
|
||||
unsigned char * body = (unsigned char*)malloc(rtmpLength);
|
||||
memset(body,0,rtmpLength);
|
||||
|
||||
/*AF 01 + AAC RAW data*/
|
||||
body[0] = 0xAF;
|
||||
body[1] = 0x01;
|
||||
memcpy(&body[2],frame.data.bytes,frame.data.length);
|
||||
[self sendPacket:RTMP_PACKET_TYPE_AUDIO data:body size:rtmpLength nTimestamp:frame.timestamp];
|
||||
free(body);
|
||||
}
|
||||
|
||||
// 断线重连
|
||||
-(void) reconnect {
|
||||
dispatch_async(YYRtmpSendQueue(), ^{
|
||||
_isReconnecting = NO;
|
||||
if(_isConnected) return;
|
||||
|
||||
[self _stop];
|
||||
[self _start];
|
||||
});
|
||||
}
|
||||
|
||||
#pragma mark -- CallBack
|
||||
void RTMPErrorCallback(RTMPError *error, void *userData){
|
||||
LFStreamRtmpSocket *socket = (__bridge LFStreamRtmpSocket*)userData;
|
||||
if(error->code < 0){
|
||||
if(socket.retryTimes4netWorkBreaken++ < socket.reconnectCount && !socket.isReconnecting){
|
||||
socket.isConnected = NO;
|
||||
socket.isConnecting = NO;
|
||||
socket.isReconnecting = YES;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(socket.reconnectInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[socket reconnect];
|
||||
});
|
||||
}else if(socket.retryTimes4netWorkBreaken >= socket.reconnectCount){
|
||||
if(socket.delegate && [socket.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[socket.delegate socketStatus:socket status:LFLiveError];
|
||||
}
|
||||
if(socket.delegate && [socket.delegate respondsToSelector:@selector(socketDidError:errorCode:)]){
|
||||
[socket.delegate socketDidError:socket errorCode:LFLiveSocketError_ReConnectTimeOut];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionTimeCallback(PILI_CONNECTION_TIME* conn_time, void *userData){
|
||||
//LFStreamRtmpSocket *socket = (__bridge LFStreamRtmpSocket*)userData;
|
||||
}
|
||||
|
||||
#pragma mark -- Getter Setter
|
||||
|
||||
- (LFStreamingBuffer*)buffer{
|
||||
if(!_buffer){
|
||||
_buffer = [[LFStreamingBuffer alloc] init];
|
||||
_buffer.delegate = self;
|
||||
}
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
- (LFLiveDebug*)debugInfo{
|
||||
if(!_debugInfo){
|
||||
_debugInfo = [[LFLiveDebug alloc] init];
|
||||
}
|
||||
return _debugInfo;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -94,9 +94,9 @@ static const NSUInteger defaultSendBufferMaxCount = 600;///< 最大缓冲区为6
|
||||
return;
|
||||
}
|
||||
|
||||
LFFrame *firstIFrame = [self firstIFrame];
|
||||
if(firstIFrame){
|
||||
[self.list removeObject:firstIFrame];
|
||||
NSArray *iFrames = [self expireIFrames];///< 删除一个I帧(但一个I帧可能对应多个nal)
|
||||
if(iFrames){
|
||||
[self.list removeObjectsInArray:iFrames];
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ static const NSUInteger defaultSendBufferMaxCount = 600;///< 最大缓冲区为6
|
||||
LFVideoFrame *videoFrame = (LFVideoFrame*)frame;
|
||||
if(videoFrame.isKeyFrame && pframes.count > 0){
|
||||
break;
|
||||
}else{
|
||||
}else if(!videoFrame.isKeyFrame){
|
||||
[pframes addObject:frame];
|
||||
}
|
||||
}
|
||||
@@ -119,14 +119,18 @@ static const NSUInteger defaultSendBufferMaxCount = 600;///< 最大缓冲区为6
|
||||
return pframes;
|
||||
}
|
||||
|
||||
- (LFFrame*)firstIFrame{
|
||||
- (NSArray*)expireIFrames{
|
||||
NSMutableArray *iframes = [[NSMutableArray alloc] init];
|
||||
uint64_t timeStamp = 0;
|
||||
for(NSInteger index = 0;index < self.list.count;index++){
|
||||
LFFrame *frame = [self.list objectAtIndex:index];
|
||||
if([frame isKindOfClass:[LFVideoFrame class]] && ((LFVideoFrame*)frame).isKeyFrame){
|
||||
return frame;
|
||||
if(timeStamp != 0 && timeStamp != frame.timestamp) break;
|
||||
[iframes addObject:frame];
|
||||
timeStamp = frame.timestamp;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
return iframes;
|
||||
}
|
||||
|
||||
NSInteger frameDataCompare(id obj1, id obj2, void *context){
|
||||
@@ -1,342 +0,0 @@
|
||||
//
|
||||
// LFStreamRtmpSocket.m
|
||||
// LFLiveKit
|
||||
//
|
||||
// Created by admin on 16/5/18.
|
||||
// Copyright © 2016年 live Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LFStreamRtmpSocket.h"
|
||||
#import "rtmp.h"
|
||||
|
||||
|
||||
#define DATA_ITEMS_MAX_COUNT 100
|
||||
#define RTMP_DATA_RESERVE_SIZE 400
|
||||
|
||||
#define RTMP_CONNECTION_TIMEOUT 1500
|
||||
#define RTMP_RECEIVE_TIMEOUT 2
|
||||
#define RTMP_HEAD_SIZE (sizeof(RTMPPacket)+RTMP_MAX_HEADER_SIZE)
|
||||
|
||||
@interface LFStreamRtmpSocket ()<LFStreamingBufferDelegate>
|
||||
{
|
||||
RTMP* _rtmp;
|
||||
}
|
||||
@property (nonatomic, weak) id<LFStreamSocketDelegate> delegate;
|
||||
@property (nonatomic, strong) LFLiveStreamInfo *stream;
|
||||
@property (nonatomic, strong) LFStreamingBuffer *buffer;
|
||||
@property (nonatomic, strong) dispatch_queue_t socketQueue;
|
||||
@property (nonatomic, assign) NSInteger retryTimes4netWorkBreaken;
|
||||
|
||||
@property (nonatomic, assign) BOOL isSending;
|
||||
@property (nonatomic, assign) BOOL isConnected;
|
||||
@property (nonatomic, assign) BOOL isConnecting;
|
||||
@property (nonatomic, assign) BOOL isReconnecting;
|
||||
|
||||
@property (nonatomic, assign) BOOL sendVideoHead;
|
||||
@property (nonatomic, assign) BOOL sendAudioHead;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LFStreamRtmpSocket
|
||||
|
||||
#pragma mark -- LFStreamSocket
|
||||
- (instancetype)initWithStream:(LFLiveStreamInfo*)stream{
|
||||
if(!stream) @throw [NSException exceptionWithName:@"LFStreamRtmpSocket init error" reason:@"stream is nil" userInfo:nil];
|
||||
if(self = [super init]){
|
||||
_stream = stream;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) start{
|
||||
dispatch_async(self.socketQueue, ^{
|
||||
if(!_stream) return;
|
||||
if(_isConnecting) return;
|
||||
if(_rtmp != NULL) return;
|
||||
|
||||
[self RTMP264_Connect:(char*)[_stream.url cStringUsingEncoding:NSASCIIStringEncoding]];
|
||||
});
|
||||
}
|
||||
|
||||
- (void) stop{
|
||||
dispatch_async(self.socketQueue, ^{
|
||||
if(_rtmp != NULL){
|
||||
RTMP_Close(_rtmp);
|
||||
RTMP_Free(_rtmp);
|
||||
_rtmp = NULL;
|
||||
}
|
||||
[self clean];
|
||||
});
|
||||
}
|
||||
|
||||
- (void) sendFrame:(LFFrame*)frame{
|
||||
__weak typeof(self) _self = self;
|
||||
dispatch_async(self.socketQueue, ^{
|
||||
__strong typeof(_self) self = _self;
|
||||
if(!frame) return;
|
||||
[self.buffer appendObject:frame];
|
||||
[self sendFrame];
|
||||
});
|
||||
}
|
||||
|
||||
- (void) setDelegate:(id<LFStreamSocketDelegate>)delegate{
|
||||
_delegate = delegate;
|
||||
}
|
||||
|
||||
#pragma mark -- CustomMethod
|
||||
- (void)sendFrame{
|
||||
if(!self.isSending && self.buffer.list.count > 0){
|
||||
self.isSending = YES;
|
||||
|
||||
if(!_isConnected || _isReconnecting || _isConnecting || !_rtmp) return;
|
||||
|
||||
// 调用发送接口
|
||||
LFFrame *frame = [self.buffer popFirstObject];
|
||||
if([frame isKindOfClass:[LFVideoFrame class]]){
|
||||
if(!self.sendVideoHead){
|
||||
self.sendVideoHead = YES;
|
||||
[self sendVideoHeader:(LFVideoFrame*)frame];
|
||||
}else{
|
||||
[self sendVideo:(LFVideoFrame*)frame];
|
||||
}
|
||||
}else{
|
||||
if(!self.sendAudioHead){
|
||||
self.sendAudioHead = YES;
|
||||
[self sendAudioHeader:(LFAudioFrame*)frame];
|
||||
}else{
|
||||
[self sendAudio:frame];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clean{
|
||||
_isConnecting = NO;
|
||||
_isReconnecting = NO;
|
||||
_isSending = NO;
|
||||
_isConnected = NO;
|
||||
_sendAudioHead = NO;
|
||||
_sendVideoHead = NO;
|
||||
[self.buffer removeAllObject];
|
||||
self.retryTimes4netWorkBreaken = 0;
|
||||
}
|
||||
|
||||
-(NSInteger) RTMP264_Connect:(char *)push_url{
|
||||
//由于摄像头的timestamp是一直在累加,需要每次得到相对时间戳
|
||||
//分配与初始化
|
||||
if(_isConnecting) return -1;
|
||||
|
||||
_isConnecting = YES;
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[self.delegate socketStatus:self status:LFLivePending];
|
||||
}
|
||||
|
||||
if(_rtmp != NULL){
|
||||
RTMP_Close(_rtmp);
|
||||
RTMP_Free(_rtmp);
|
||||
}
|
||||
|
||||
_rtmp = RTMP_Alloc();
|
||||
RTMP_Init(_rtmp);
|
||||
|
||||
//设置URL
|
||||
if (RTMP_SetupURL(_rtmp, push_url) < 0){
|
||||
//log(LOG_ERR, "RTMP_SetupURL() failed!");
|
||||
goto Failed;
|
||||
}
|
||||
|
||||
//设置可写,即发布流,这个函数必须在连接前使用,否则无效
|
||||
RTMP_EnableWrite(_rtmp);
|
||||
_rtmp->Link.timeout = RTMP_RECEIVE_TIMEOUT;
|
||||
|
||||
//连接服务器
|
||||
if (RTMP_Connect(_rtmp, NULL) < 0){
|
||||
goto Failed;
|
||||
}
|
||||
|
||||
//连接流
|
||||
if (RTMP_ConnectStream(_rtmp, 0) < 0) {
|
||||
goto Failed;
|
||||
}
|
||||
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[self.delegate socketStatus:self status:LFLiveStart];
|
||||
}
|
||||
|
||||
_isConnected = YES;
|
||||
_isConnecting = NO;
|
||||
_isReconnecting = NO;
|
||||
_isSending = NO;
|
||||
_retryTimes4netWorkBreaken = 0;
|
||||
return 0;
|
||||
|
||||
Failed:
|
||||
RTMP_Close(_rtmp);
|
||||
RTMP_Free(_rtmp);
|
||||
[self clean];
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[self.delegate socketStatus:self status:LFLiveError];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#pragma mark -- Rtmp Send
|
||||
- (void)sendVideoHeader:(LFVideoFrame*)videoFrame{
|
||||
if(!videoFrame || !videoFrame.sps || !videoFrame.pps) return;
|
||||
|
||||
unsigned char * body=NULL;
|
||||
NSInteger iIndex = 0;
|
||||
NSInteger rtmpLength = 1024;
|
||||
const char *sps = videoFrame.sps.bytes;
|
||||
const char *pps = videoFrame.pps.bytes;
|
||||
NSInteger sps_len = videoFrame.sps.length;
|
||||
NSInteger pps_len = videoFrame.pps.length;
|
||||
|
||||
body = (unsigned char*)malloc(rtmpLength);
|
||||
memset(body,0,rtmpLength);
|
||||
|
||||
body[iIndex++] = 0x17;
|
||||
body[iIndex++] = 0x00;
|
||||
|
||||
body[iIndex++] = 0x00;
|
||||
body[iIndex++] = 0x00;
|
||||
body[iIndex++] = 0x00;
|
||||
|
||||
body[iIndex++] = 0x01;
|
||||
body[iIndex++] = sps[1];
|
||||
body[iIndex++] = sps[2];
|
||||
body[iIndex++] = sps[3];
|
||||
body[iIndex++] = 0xff;
|
||||
|
||||
/*sps*/
|
||||
body[iIndex++] = 0xe1;
|
||||
body[iIndex++] = (sps_len >> 8) & 0xff;
|
||||
body[iIndex++] = sps_len & 0xff;
|
||||
memcpy(&body[iIndex],sps,sps_len);
|
||||
iIndex += sps_len;
|
||||
|
||||
/*pps*/
|
||||
body[iIndex++] = 0x01;
|
||||
body[iIndex++] = (pps_len >> 8) & 0xff;
|
||||
body[iIndex++] = (pps_len) & 0xff;
|
||||
memcpy(&body[iIndex], pps, pps_len);
|
||||
iIndex += pps_len;
|
||||
|
||||
[self sendPacket:RTMP_PACKET_TYPE_VIDEO data:body size:iIndex nTimestamp:0];
|
||||
free(body);
|
||||
}
|
||||
|
||||
|
||||
- (void)sendVideo:(LFVideoFrame*)frame{
|
||||
if(!frame || !frame.data || frame.data.length < 11) return;
|
||||
|
||||
NSInteger i = 0;
|
||||
NSInteger rtmpLength = frame.data.length+9;
|
||||
unsigned char *body = (unsigned char*)malloc(rtmpLength);
|
||||
memset(body,0,rtmpLength);
|
||||
|
||||
if(frame.isKeyFrame){
|
||||
body[i++] = 0x17;// 1:Iframe 7:AVC
|
||||
} else{
|
||||
body[i++] = 0x27;// 2:Pframe 7:AVC
|
||||
}
|
||||
body[i++] = 0x01;// AVC NALU
|
||||
body[i++] = 0x00;
|
||||
body[i++] = 0x00;
|
||||
body[i++] = 0x00;
|
||||
body[i++] = (frame.data.length >> 24) & 0xff;
|
||||
body[i++] = (frame.data.length >> 16) & 0xff;
|
||||
body[i++] = (frame.data.length >> 8) & 0xff;
|
||||
body[i++] = (frame.data.length ) & 0xff;
|
||||
memcpy(&body[i],frame.data.bytes,frame.data.length);
|
||||
|
||||
[self sendPacket:RTMP_PACKET_TYPE_VIDEO data:body size:(rtmpLength) nTimestamp:frame.timestamp];
|
||||
free(body);
|
||||
}
|
||||
|
||||
-(NSInteger) sendPacket:(unsigned int)nPacketType data:(unsigned char *)data size:(NSInteger) size nTimestamp:(uint64_t) nTimestamp{
|
||||
NSInteger rtmpLength = size;
|
||||
RTMPPacket rtmp_pack;
|
||||
RTMPPacket_Reset(&rtmp_pack);
|
||||
RTMPPacket_Alloc(&rtmp_pack,(uint32_t)rtmpLength);
|
||||
|
||||
rtmp_pack.m_nBodySize = (uint32_t)size;
|
||||
memcpy(rtmp_pack.m_body,data,size);
|
||||
rtmp_pack.m_hasAbsTimestamp = 0;
|
||||
rtmp_pack.m_packetType = nPacketType;
|
||||
if(_rtmp) rtmp_pack.m_nInfoField2 = _rtmp->m_stream_id;
|
||||
rtmp_pack.m_nChannel = 0x04;
|
||||
rtmp_pack.m_headerType = RTMP_PACKET_SIZE_LARGE;
|
||||
if (RTMP_PACKET_TYPE_AUDIO == nPacketType && size !=4){
|
||||
rtmp_pack.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
|
||||
}
|
||||
rtmp_pack.m_nTimeStamp = (uint32_t)nTimestamp;
|
||||
|
||||
NSInteger nRet = [self RtmpPacketSend:&rtmp_pack];
|
||||
|
||||
RTMPPacket_Free(&rtmp_pack);
|
||||
return nRet;
|
||||
}
|
||||
|
||||
- (NSInteger)RtmpPacketSend:(RTMPPacket*)packet{
|
||||
if (RTMP_IsConnected(_rtmp)){
|
||||
int success = RTMP_SendPacket(_rtmp,packet,0);
|
||||
if(success){
|
||||
self.isSending = NO;
|
||||
[self sendFrame];
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
- (void)sendAudioHeader:(LFAudioFrame*)audioFrame{
|
||||
if(!audioFrame || !audioFrame.audioInfo) return;
|
||||
|
||||
NSInteger rtmpLength = audioFrame.audioInfo.length + 2;/*spec data长度,一般是2*/
|
||||
unsigned char * body = (unsigned char*)malloc(rtmpLength);
|
||||
memset(body,0,rtmpLength);
|
||||
|
||||
/*AF 00 + AAC RAW data*/
|
||||
body[0] = 0xAF;
|
||||
body[1] = 0x00;
|
||||
memcpy(&body[2],audioFrame.audioInfo.bytes,audioFrame.audioInfo.length); /*spec_buf是AAC sequence header数据*/
|
||||
[self sendPacket:RTMP_PACKET_TYPE_AUDIO data:body size:rtmpLength nTimestamp:0];
|
||||
free(body);
|
||||
}
|
||||
|
||||
- (void)sendAudio:(LFFrame*)frame{
|
||||
if(!frame) return;
|
||||
|
||||
NSInteger rtmpLength = frame.data.length + 2;/*spec data长度,一般是2*/
|
||||
unsigned char * body = (unsigned char*)malloc(rtmpLength);
|
||||
memset(body,0,rtmpLength);
|
||||
|
||||
/*AF 01 + AAC RAW data*/
|
||||
body[0] = 0xAF;
|
||||
body[1] = 0x01;
|
||||
memcpy(&body[2],frame.data.bytes,frame.data.length);
|
||||
[self sendPacket:RTMP_PACKET_TYPE_AUDIO data:body size:rtmpLength nTimestamp:frame.timestamp];
|
||||
free(body);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -- Getter Setter
|
||||
- (dispatch_queue_t)socketQueue{
|
||||
if(!_socketQueue){
|
||||
_socketQueue = dispatch_queue_create("com.youku.LaiFeng.live.socketQueue", NULL);
|
||||
}
|
||||
return _socketQueue;
|
||||
}
|
||||
|
||||
- (LFStreamingBuffer*)buffer{
|
||||
if(!_buffer){
|
||||
_buffer = [[LFStreamingBuffer alloc] init];
|
||||
_buffer.delegate = self;
|
||||
}
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// LFStreamTcpSocket.h
|
||||
// LFLiveKit
|
||||
//
|
||||
// Created by admin on 16/5/3.
|
||||
// Copyright © 2016年 倾慕. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LFStreamSocket.h"
|
||||
|
||||
@interface LFStreamTcpSocket : NSObject<LFStreamSocket>
|
||||
#pragma mark - Initializer
|
||||
///=============================================================================
|
||||
/// @name Initializer
|
||||
///=============================================================================
|
||||
- (nullable instancetype)init UNAVAILABLE_ATTRIBUTE;
|
||||
+ (nullable instancetype)new UNAVAILABLE_ATTRIBUTE;
|
||||
|
||||
@end
|
||||
@@ -1,331 +0,0 @@
|
||||
//
|
||||
// LFStreamTcpSocket.m
|
||||
// LFLiveKit
|
||||
//
|
||||
// Created by admin on 16/5/3.
|
||||
// Copyright © 2016年 倾慕. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LFStreamTcpSocket.h"
|
||||
#import "GCDAsyncSocket.h"
|
||||
#import "LFFlvPackage.h"
|
||||
|
||||
static const NSInteger RetryTimesBreaken = 20;///< 重连3分钟 3秒一次 一共60次
|
||||
static const NSInteger RetryTimesMargin = 3;
|
||||
const NSInteger TCP_RECEIVE_TIMEOUT = -1;
|
||||
|
||||
@interface LFStreamTcpSocket () <LFStreamingBufferDelegate,GCDAsyncSocketDelegate>
|
||||
|
||||
@property (nonatomic, strong) GCDAsyncSocket * socket;
|
||||
@property (nonatomic, strong) dispatch_queue_t socketQueue;
|
||||
@property (nonatomic, strong) LFStreamingBuffer *buffer;
|
||||
@property (nonatomic, strong) LFLiveStreamInfo *stream;
|
||||
@property (nonatomic, weak) id<LFStreamSocketDelegate> delegate;
|
||||
@property (nonatomic, strong) id<LFStreamPackage> package;
|
||||
@property (nonatomic, strong) LFLiveDebug *debugInfo;
|
||||
@property (nonatomic, assign) CGSize videoSize;
|
||||
|
||||
@property (nonatomic, assign) BOOL isSending;
|
||||
@property (nonatomic, assign) BOOL isConnecting;
|
||||
@property (nonatomic, assign) BOOL isReconnecting;
|
||||
@property (nonatomic, assign) BOOL isConnected;
|
||||
@property (nonatomic, assign) NSInteger retryTimes4netWorkBreaken;
|
||||
@property (nonatomic, assign) NSInteger reconnectInterval;
|
||||
@property (nonatomic, assign) NSInteger reconnectCount;
|
||||
@property (nonatomic, assign) BOOL needSendHeader;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LFStreamTcpSocket
|
||||
|
||||
- (nullable instancetype)initWithStream:(nullable LFLiveStreamInfo*)stream videoSize:(CGSize)videoSize reconnectInterval:(NSInteger)reconnectInterval reconnectCount:(NSInteger)reconnectCount{
|
||||
if(!stream) @throw [NSException exceptionWithName:@"LFStreamTcpSocket init error" reason:@"stream is nil" userInfo:nil];
|
||||
if(CGSizeEqualToSize(videoSize, CGSizeZero)) @throw [NSException exceptionWithName:@"LFStreamTcpSocket init error" reason:@"videoSize is zero" userInfo:nil];
|
||||
if(self = [super init]){
|
||||
_stream = stream;
|
||||
_videoSize = videoSize;
|
||||
if(reconnectInterval > 0) _reconnectInterval = reconnectInterval;
|
||||
else _reconnectInterval = RetryTimesMargin;
|
||||
|
||||
if(reconnectCount > 0) _reconnectCount = reconnectCount;
|
||||
else _reconnectCount = RetryTimesBreaken;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark -- LFStreamSocket
|
||||
- (void) start{
|
||||
if(!_stream) return;
|
||||
if(_isConnecting) return;
|
||||
if(_socket.isConnected) return;
|
||||
[self clean];
|
||||
|
||||
self.debugInfo.streamId = self.stream.streamId;
|
||||
self.debugInfo.uploadUrl = self.stream.url;
|
||||
self.debugInfo.videoSize = self.videoSize;
|
||||
self.debugInfo.isRtmp = NO;
|
||||
|
||||
if(![self.socket connectToHost:_stream.host onPort:_stream.port withTimeout:5 error:nil]){
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[self.delegate socketStatus:self status:LFLiveError];
|
||||
}
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketDidError:errorCode:)]){
|
||||
[self.delegate socketDidError:self errorCode:LFLiveSocketError_ConnectSocket];
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[self.delegate socketStatus:self status:LFLivePending];
|
||||
}
|
||||
_isConnecting = YES;
|
||||
|
||||
}
|
||||
|
||||
- (void) stop{
|
||||
[self.socket disconnect];
|
||||
[self clean];
|
||||
}
|
||||
|
||||
- (void)sendFrame:(LFFrame *)frame{
|
||||
__weak typeof(self) _self = self;
|
||||
dispatch_async(self.socketQueue, ^{
|
||||
__strong typeof(_self) self = _self;
|
||||
if(!frame) return;
|
||||
if([frame isKindOfClass:[LFAudioFrame class]]){
|
||||
NSData *packageData = [self.package aaCPacket:(LFAudioFrame*)frame];///< 打包flv
|
||||
if(!packageData) return;
|
||||
frame.data = packageData;
|
||||
}else{
|
||||
NSData *packageData = [self.package h264Packet:(LFVideoFrame*)frame];///< 打包flv
|
||||
if(!packageData) return;
|
||||
frame.data = packageData;
|
||||
}
|
||||
|
||||
[self.buffer appendObject:frame];
|
||||
[self sendFrame];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id<LFStreamSocketDelegate>)delegate{
|
||||
_delegate = delegate;
|
||||
}
|
||||
|
||||
#pragma mark -- CustomMethod
|
||||
- (void)sendFrame{
|
||||
if(!self.isSending && self.buffer.list.count > 0 && _isConnected){
|
||||
self.isSending = YES;
|
||||
LFFrame *frame = [self.buffer popFirstObject];
|
||||
if(self.needSendHeader){///< flvHeader 插入到队列最前面去
|
||||
NSMutableData * mutableData = [[NSMutableData alloc] init];
|
||||
[mutableData appendData:frame.header];
|
||||
[mutableData appendData:frame.data];
|
||||
frame.data = mutableData;
|
||||
self.needSendHeader = NO;
|
||||
}
|
||||
[self.socket writeData:frame.data withTimeout:TCP_RECEIVE_TIMEOUT tag:1];
|
||||
|
||||
self.debugInfo.dataFlow += frame.data.length;
|
||||
if(CACurrentMediaTime()*1000 - self.debugInfo.timeStamp < 1000) {
|
||||
self.debugInfo.bandwidth += frame.data.length;
|
||||
if([frame isKindOfClass:[LFAudioFrame class]]){
|
||||
self.debugInfo.capturedAudioCount ++;
|
||||
}else{
|
||||
self.debugInfo.capturedVideoCount ++;
|
||||
}
|
||||
self.debugInfo.unSendCount = self.buffer.list.count;
|
||||
}else {
|
||||
self.debugInfo.currentBandwidth = self.debugInfo.bandwidth;
|
||||
self.debugInfo.currentCapturedAudioCount = self.debugInfo.capturedAudioCount;
|
||||
self.debugInfo.currentCapturedVideoCount = self.debugInfo.capturedVideoCount;
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketDebug:debugInfo:)]){
|
||||
[self.delegate socketDebug:self debugInfo:self.debugInfo];
|
||||
}
|
||||
|
||||
self.debugInfo.bandwidth = 0;
|
||||
self.debugInfo.capturedAudioCount = 0;
|
||||
self.debugInfo.capturedVideoCount = 0;
|
||||
self.debugInfo.timeStamp = CACurrentMediaTime()*1000;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clean{
|
||||
_isConnected = NO;
|
||||
_isConnecting = NO;
|
||||
_isReconnecting = NO;
|
||||
_isSending = NO;
|
||||
_retryTimes4netWorkBreaken = 0;
|
||||
_needSendHeader = NO;
|
||||
self.debugInfo = nil;
|
||||
[self.buffer removeAllObject];
|
||||
}
|
||||
|
||||
// 断线重连
|
||||
-(void) reconnect {
|
||||
_isReconnecting = NO;
|
||||
if(_isConnected) return;
|
||||
if([self.socket isConnected]) return;
|
||||
|
||||
if(![self.socket connectToHost:_stream.host onPort:_stream.port withTimeout:5 error:nil]){
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[self.delegate socketStatus:self status:LFLiveError];
|
||||
}
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketDidError:errorCode:)]){
|
||||
[self.delegate socketDidError:self errorCode:LFLiveSocketError_ConnectSocket];
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -- GCDAsyncSocketDelegate
|
||||
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
|
||||
NSLog(@"onSocket:%p didConnectToHost:%@ port:%hu", sock, host, port);
|
||||
[sock readDataWithTimeout:-1 tag:0];
|
||||
if(_isConnected) return;
|
||||
[self.socket writeData:self.verificationData withTimeout:-1 tag:0];
|
||||
}
|
||||
|
||||
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
|
||||
[sock readDataWithTimeout:-1 tag:0];
|
||||
if(_isConnected) return;
|
||||
if([self verificationDataValid:data]){
|
||||
NSLog(@"服务器验证成功,准备发送数据");
|
||||
_isConnected = YES;
|
||||
_isConnecting = NO;
|
||||
_isReconnecting = NO;
|
||||
_retryTimes4netWorkBreaken = 0;// 计数器清零
|
||||
_needSendHeader = YES;
|
||||
self.isSending = NO;
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[self.delegate socketStatus:self status:LFLiveStart];
|
||||
}
|
||||
}else{
|
||||
NSLog(@"服务器验证失败");
|
||||
[self clean];
|
||||
[self.socket disconnect];
|
||||
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[self.delegate socketStatus:self status:LFLiveError];
|
||||
}
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketDidError:errorCode:)]){
|
||||
[self.delegate socketDidError:self errorCode:LFLiveSocketError_Verification];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
|
||||
NSLog(@"onSocket:%p socketDidDisconnectWithError:%@", sock, err);
|
||||
if(err){
|
||||
if(self.retryTimes4netWorkBreaken++ < _reconnectCount && !self.isReconnecting){
|
||||
_isConnected = NO;
|
||||
_isConnecting = NO;
|
||||
_isReconnecting = YES;
|
||||
|
||||
[self.socket disconnect];
|
||||
///< 连接超时
|
||||
if(err.code == 3){
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[self.delegate socketStatus:self status:LFLiveError];
|
||||
}
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketDidError:errorCode:)]){
|
||||
[self.delegate socketDidError:self errorCode:LFLiveSocketError_ConnectSocket];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_reconnectInterval * NSEC_PER_SEC)), self.socketQueue, ^{
|
||||
[self reconnect];
|
||||
});
|
||||
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[self.delegate socketStatus:self status:LFLivePending];
|
||||
}
|
||||
}else if(self.retryTimes4netWorkBreaken >= _reconnectCount){
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[self.delegate socketStatus:self status:LFLiveError];
|
||||
}
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketDidError:errorCode:)]){
|
||||
[self.delegate socketDidError:self errorCode:LFLiveSocketError_ReConnectTimeOut];
|
||||
}
|
||||
}
|
||||
}else{
|
||||
[self clean];
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketStatus:status:)]){
|
||||
[self.delegate socketStatus:self status:LFLiveStop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
|
||||
if(tag > 0){
|
||||
self.isSending = NO;
|
||||
[self sendFrame];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark --BufferDelegate
|
||||
- (void)streamingBuffer:(nullable LFStreamingBuffer*)buffer bufferState:(LFLiveBuffferState)state{
|
||||
if(self.isConnected){
|
||||
if(self.delegate && [self.delegate respondsToSelector:@selector(socketBufferStatus:status:)]){
|
||||
[self.delegate socketBufferStatus:self status:state];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -- Getter Setter
|
||||
- (dispatch_queue_t)socketQueue{
|
||||
if(!_socketQueue){
|
||||
_socketQueue = dispatch_queue_create("com.youku.LaiFeng.live.socketQueue", NULL);
|
||||
}
|
||||
return _socketQueue;
|
||||
}
|
||||
|
||||
- (GCDAsyncSocket*)socket{
|
||||
if(!_socket){
|
||||
_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:self.socketQueue socketQueue:self.socketQueue];
|
||||
}
|
||||
return _socket;
|
||||
}
|
||||
|
||||
- (LFStreamingBuffer*)buffer{
|
||||
if(!_buffer){
|
||||
_buffer = [[LFStreamingBuffer alloc] init];
|
||||
_buffer.delegate = self;
|
||||
}
|
||||
return _buffer;
|
||||
}
|
||||
|
||||
- (id<LFStreamPackage>)package{
|
||||
if(!_package){
|
||||
_package = [[LFFlvPackage alloc] initWithVideoSize:self.videoSize];
|
||||
}
|
||||
return _package;
|
||||
}
|
||||
|
||||
- (LFLiveDebug*)debugInfo{
|
||||
if(!_debugInfo){
|
||||
_debugInfo = [[LFLiveDebug alloc] init];
|
||||
}
|
||||
return _debugInfo;
|
||||
}
|
||||
|
||||
#pragma mark -- 服务器验证
|
||||
- (NSData*)verificationData{
|
||||
/** 结构体专为NSData **/
|
||||
if(!self.stream) return nil;
|
||||
#warning TODO send verficationData to server
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)verificationDataValid:(NSData*)data{
|
||||
/** NSData专为结构体 **/
|
||||
if(!self.stream) return NO;
|
||||
if(!data) return NO;
|
||||
#warning TODO server give client data,verification
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
#CocoaPods
|
||||
Pods/
|
||||
Podfile.lock
|
||||
@@ -0,0 +1,474 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
57B42059E84CC681C5C99B68 /* libPods-LFLiveKitDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D506B5639A1D45519536773 /* libPods-LFLiveKitDemo.a */; };
|
||||
840762F11D07C7D0000FD0BF /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 840762F01D07C7D0000FD0BF /* main.m */; };
|
||||
840762F41D07C7D0000FD0BF /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 840762F31D07C7D0000FD0BF /* AppDelegate.m */; };
|
||||
840762F71D07C7D0000FD0BF /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 840762F61D07C7D0000FD0BF /* ViewController.m */; };
|
||||
840762FC1D07C7D0000FD0BF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 840762FB1D07C7D0000FD0BF /* Assets.xcassets */; };
|
||||
840762FF1D07C7D0000FD0BF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 840762FD1D07C7D0000FD0BF /* LaunchScreen.storyboard */; };
|
||||
840763291D07C894000FD0BF /* UIControl+YYAdd.m in Sources */ = {isa = PBXBuildFile; fileRef = 840763241D07C894000FD0BF /* UIControl+YYAdd.m */; };
|
||||
8407632A1D07C894000FD0BF /* UIView+YYAdd.m in Sources */ = {isa = PBXBuildFile; fileRef = 840763261D07C894000FD0BF /* UIView+YYAdd.m */; };
|
||||
8407632B1D07C894000FD0BF /* LFLivePreview.m in Sources */ = {isa = PBXBuildFile; fileRef = 840763281D07C894000FD0BF /* LFLivePreview.m */; };
|
||||
840763351D07C899000FD0BF /* camra_beauty@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8407632D1D07C899000FD0BF /* camra_beauty@2x.png */; };
|
||||
840763361D07C899000FD0BF /* camra_beauty@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8407632E1D07C899000FD0BF /* camra_beauty@3x.png */; };
|
||||
840763371D07C899000FD0BF /* camra_beauty_close@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8407632F1D07C899000FD0BF /* camra_beauty_close@2x.png */; };
|
||||
840763381D07C899000FD0BF /* camra_beauty_close@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 840763301D07C899000FD0BF /* camra_beauty_close@3x.png */; };
|
||||
840763391D07C899000FD0BF /* camra_preview@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 840763311D07C899000FD0BF /* camra_preview@2x.png */; };
|
||||
8407633A1D07C899000FD0BF /* camra_preview@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 840763321D07C899000FD0BF /* camra_preview@3x.png */; };
|
||||
8407633B1D07C899000FD0BF /* close_preview@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 840763331D07C899000FD0BF /* close_preview@2x.png */; };
|
||||
8407633C1D07C899000FD0BF /* close_preview@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 840763341D07C899000FD0BF /* close_preview@3x.png */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
195672426061368F86F1F4FA /* Pods-LFLiveKitDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LFLiveKitDemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-LFLiveKitDemo/Pods-LFLiveKitDemo.release.xcconfig"; sourceTree = "<group>"; };
|
||||
5D506B5639A1D45519536773 /* libPods-LFLiveKitDemo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-LFLiveKitDemo.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7FAA55DD93CD7AB58E7A977A /* Pods-LFLiveKitDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LFLiveKitDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LFLiveKitDemo/Pods-LFLiveKitDemo.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
840762EC1D07C7D0000FD0BF /* LFLiveKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LFLiveKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
840762F01D07C7D0000FD0BF /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
840762F21D07C7D0000FD0BF /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||
840762F31D07C7D0000FD0BF /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
||||
840762F51D07C7D0000FD0BF /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
|
||||
840762F61D07C7D0000FD0BF /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
|
||||
840762FB1D07C7D0000FD0BF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
840762FE1D07C7D0000FD0BF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
840763001D07C7D0000FD0BF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
840763091D07C7D0000FD0BF /* LFLiveKitDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LFLiveKitDemoTests.m; sourceTree = "<group>"; };
|
||||
8407630B1D07C7D0000FD0BF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
840763141D07C7D0000FD0BF /* LFLiveKitDemoUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LFLiveKitDemoUITests.m; sourceTree = "<group>"; };
|
||||
840763161D07C7D0000FD0BF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
840763231D07C894000FD0BF /* UIControl+YYAdd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIControl+YYAdd.h"; sourceTree = "<group>"; };
|
||||
840763241D07C894000FD0BF /* UIControl+YYAdd.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIControl+YYAdd.m"; sourceTree = "<group>"; };
|
||||
840763251D07C894000FD0BF /* UIView+YYAdd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+YYAdd.h"; sourceTree = "<group>"; };
|
||||
840763261D07C894000FD0BF /* UIView+YYAdd.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+YYAdd.m"; sourceTree = "<group>"; };
|
||||
840763271D07C894000FD0BF /* LFLivePreview.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LFLivePreview.h; sourceTree = "<group>"; };
|
||||
840763281D07C894000FD0BF /* LFLivePreview.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LFLivePreview.m; sourceTree = "<group>"; };
|
||||
8407632D1D07C899000FD0BF /* camra_beauty@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "camra_beauty@2x.png"; sourceTree = "<group>"; };
|
||||
8407632E1D07C899000FD0BF /* camra_beauty@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "camra_beauty@3x.png"; sourceTree = "<group>"; };
|
||||
8407632F1D07C899000FD0BF /* camra_beauty_close@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "camra_beauty_close@2x.png"; sourceTree = "<group>"; };
|
||||
840763301D07C899000FD0BF /* camra_beauty_close@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "camra_beauty_close@3x.png"; sourceTree = "<group>"; };
|
||||
840763311D07C899000FD0BF /* camra_preview@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "camra_preview@2x.png"; sourceTree = "<group>"; };
|
||||
840763321D07C899000FD0BF /* camra_preview@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "camra_preview@3x.png"; sourceTree = "<group>"; };
|
||||
840763331D07C899000FD0BF /* close_preview@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "close_preview@2x.png"; sourceTree = "<group>"; };
|
||||
840763341D07C899000FD0BF /* close_preview@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "close_preview@3x.png"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
840762E91D07C7D0000FD0BF /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
57B42059E84CC681C5C99B68 /* libPods-LFLiveKitDemo.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
713DA9EBCA308093C74917F9 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5D506B5639A1D45519536773 /* libPods-LFLiveKitDemo.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
840762E31D07C7D0000FD0BF = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
840762EE1D07C7D0000FD0BF /* LFLiveKitDemo */,
|
||||
840763081D07C7D0000FD0BF /* LFLiveKitDemoTests */,
|
||||
840763131D07C7D0000FD0BF /* LFLiveKitDemoUITests */,
|
||||
840762ED1D07C7D0000FD0BF /* Products */,
|
||||
9BA1F10CECEAF692D0035AED /* Pods */,
|
||||
713DA9EBCA308093C74917F9 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
840762ED1D07C7D0000FD0BF /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
840762EC1D07C7D0000FD0BF /* LFLiveKitDemo.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
840762EE1D07C7D0000FD0BF /* LFLiveKitDemo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
840763221D07C894000FD0BF /* category */,
|
||||
840763271D07C894000FD0BF /* LFLivePreview.h */,
|
||||
840763281D07C894000FD0BF /* LFLivePreview.m */,
|
||||
840762F21D07C7D0000FD0BF /* AppDelegate.h */,
|
||||
840762F31D07C7D0000FD0BF /* AppDelegate.m */,
|
||||
840762F51D07C7D0000FD0BF /* ViewController.h */,
|
||||
840762F61D07C7D0000FD0BF /* ViewController.m */,
|
||||
840762FB1D07C7D0000FD0BF /* Assets.xcassets */,
|
||||
840762FD1D07C7D0000FD0BF /* LaunchScreen.storyboard */,
|
||||
840763001D07C7D0000FD0BF /* Info.plist */,
|
||||
840762EF1D07C7D0000FD0BF /* Supporting Files */,
|
||||
);
|
||||
path = LFLiveKitDemo;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
840762EF1D07C7D0000FD0BF /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8407632C1D07C899000FD0BF /* images */,
|
||||
840762F01D07C7D0000FD0BF /* main.m */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
840763081D07C7D0000FD0BF /* LFLiveKitDemoTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
840763091D07C7D0000FD0BF /* LFLiveKitDemoTests.m */,
|
||||
8407630B1D07C7D0000FD0BF /* Info.plist */,
|
||||
);
|
||||
path = LFLiveKitDemoTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
840763131D07C7D0000FD0BF /* LFLiveKitDemoUITests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
840763141D07C7D0000FD0BF /* LFLiveKitDemoUITests.m */,
|
||||
840763161D07C7D0000FD0BF /* Info.plist */,
|
||||
);
|
||||
path = LFLiveKitDemoUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
840763221D07C894000FD0BF /* category */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
840763231D07C894000FD0BF /* UIControl+YYAdd.h */,
|
||||
840763241D07C894000FD0BF /* UIControl+YYAdd.m */,
|
||||
840763251D07C894000FD0BF /* UIView+YYAdd.h */,
|
||||
840763261D07C894000FD0BF /* UIView+YYAdd.m */,
|
||||
);
|
||||
path = category;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8407632C1D07C899000FD0BF /* images */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8407632D1D07C899000FD0BF /* camra_beauty@2x.png */,
|
||||
8407632E1D07C899000FD0BF /* camra_beauty@3x.png */,
|
||||
8407632F1D07C899000FD0BF /* camra_beauty_close@2x.png */,
|
||||
840763301D07C899000FD0BF /* camra_beauty_close@3x.png */,
|
||||
840763311D07C899000FD0BF /* camra_preview@2x.png */,
|
||||
840763321D07C899000FD0BF /* camra_preview@3x.png */,
|
||||
840763331D07C899000FD0BF /* close_preview@2x.png */,
|
||||
840763341D07C899000FD0BF /* close_preview@3x.png */,
|
||||
);
|
||||
path = images;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9BA1F10CECEAF692D0035AED /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7FAA55DD93CD7AB58E7A977A /* Pods-LFLiveKitDemo.debug.xcconfig */,
|
||||
195672426061368F86F1F4FA /* Pods-LFLiveKitDemo.release.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
840762EB1D07C7D0000FD0BF /* LFLiveKitDemo */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 840763191D07C7D0000FD0BF /* Build configuration list for PBXNativeTarget "LFLiveKitDemo" */;
|
||||
buildPhases = (
|
||||
7E4C7C5523618A0595228010 /* 📦 Check Pods Manifest.lock */,
|
||||
840762E81D07C7D0000FD0BF /* Sources */,
|
||||
840762E91D07C7D0000FD0BF /* Frameworks */,
|
||||
840762EA1D07C7D0000FD0BF /* Resources */,
|
||||
E4007BB7D4B0E165011BF22F /* 📦 Embed Pods Frameworks */,
|
||||
64FCFF97E6544B1C8F282394 /* 📦 Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = LFLiveKitDemo;
|
||||
productName = LFLiveKitDemo;
|
||||
productReference = 840762EC1D07C7D0000FD0BF /* LFLiveKitDemo.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
840762E41D07C7D0000FD0BF /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0730;
|
||||
ORGANIZATIONNAME = admin;
|
||||
TargetAttributes = {
|
||||
840762EB1D07C7D0000FD0BF = {
|
||||
CreatedOnToolsVersion = 7.3;
|
||||
SystemCapabilities = {
|
||||
com.apple.BackgroundModes = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 840762E71D07C7D0000FD0BF /* Build configuration list for PBXProject "LFLiveKitDemo" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 840762E31D07C7D0000FD0BF;
|
||||
productRefGroup = 840762ED1D07C7D0000FD0BF /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
840762EB1D07C7D0000FD0BF /* LFLiveKitDemo */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
840762EA1D07C7D0000FD0BF /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
840763371D07C899000FD0BF /* camra_beauty_close@2x.png in Resources */,
|
||||
840762FF1D07C7D0000FD0BF /* LaunchScreen.storyboard in Resources */,
|
||||
840763351D07C899000FD0BF /* camra_beauty@2x.png in Resources */,
|
||||
840762FC1D07C7D0000FD0BF /* Assets.xcassets in Resources */,
|
||||
8407633A1D07C899000FD0BF /* camra_preview@3x.png in Resources */,
|
||||
840763381D07C899000FD0BF /* camra_beauty_close@3x.png in Resources */,
|
||||
8407633C1D07C899000FD0BF /* close_preview@3x.png in Resources */,
|
||||
8407633B1D07C899000FD0BF /* close_preview@2x.png in Resources */,
|
||||
840763391D07C899000FD0BF /* camra_preview@2x.png in Resources */,
|
||||
840763361D07C899000FD0BF /* camra_beauty@3x.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
64FCFF97E6544B1C8F282394 /* 📦 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-LFLiveKitDemo/Pods-LFLiveKitDemo-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
7E4C7C5523618A0595228010 /* 📦 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;
|
||||
};
|
||||
E4007BB7D4B0E165011BF22F /* 📦 Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "📦 Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LFLiveKitDemo/Pods-LFLiveKitDemo-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
840762E81D07C7D0000FD0BF /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8407632B1D07C894000FD0BF /* LFLivePreview.m in Sources */,
|
||||
840762F71D07C7D0000FD0BF /* ViewController.m in Sources */,
|
||||
840763291D07C894000FD0BF /* UIControl+YYAdd.m in Sources */,
|
||||
840762F41D07C7D0000FD0BF /* AppDelegate.m in Sources */,
|
||||
8407632A1D07C894000FD0BF /* UIView+YYAdd.m in Sources */,
|
||||
840762F11D07C7D0000FD0BF /* main.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
840762FD1D07C7D0000FD0BF /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
840762FE1D07C7D0000FD0BF /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
840763171D07C7D0000FD0BF /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
840763181D07C7D0000FD0BF /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
8407631A1D07C7D0000FD0BF /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7FAA55DD93CD7AB58E7A977A /* Pods-LFLiveKitDemo.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = LFLiveKitDemo/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.youku.LFLiveKit.LFLiveKitDemo;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
8407631B1D07C7D0000FD0BF /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 195672426061368F86F1F4FA /* Pods-LFLiveKitDemo.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = LFLiveKitDemo/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.youku.LFLiveKit.LFLiveKitDemo;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
840762E71D07C7D0000FD0BF /* Build configuration list for PBXProject "LFLiveKitDemo" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
840763171D07C7D0000FD0BF /* Debug */,
|
||||
840763181D07C7D0000FD0BF /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
840763191D07C7D0000FD0BF /* Build configuration list for PBXNativeTarget "LFLiveKitDemo" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
8407631A1D07C7D0000FD0BF /* Debug */,
|
||||
8407631B1D07C7D0000FD0BF /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 840762E41D07C7D0000FD0BF /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:LFLiveKitDemo.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0730"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "840762EB1D07C7D0000FD0BF"
|
||||
BuildableName = "LFLiveKitDemo.app"
|
||||
BlueprintName = "LFLiveKitDemo"
|
||||
ReferencedContainer = "container:LFLiveKitDemo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "840763041D07C7D0000FD0BF"
|
||||
BuildableName = "LFLiveKitDemoTests.xctest"
|
||||
BlueprintName = "LFLiveKitDemoTests"
|
||||
ReferencedContainer = "container:LFLiveKitDemo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "8407630F1D07C7D0000FD0BF"
|
||||
BuildableName = "LFLiveKitDemoUITests.xctest"
|
||||
BlueprintName = "LFLiveKitDemoUITests"
|
||||
ReferencedContainer = "container:LFLiveKitDemo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "840762EB1D07C7D0000FD0BF"
|
||||
BuildableName = "LFLiveKitDemo.app"
|
||||
BlueprintName = "LFLiveKitDemo"
|
||||
ReferencedContainer = "container:LFLiveKitDemo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "840762EB1D07C7D0000FD0BF"
|
||||
BuildableName = "LFLiveKitDemo.app"
|
||||
BlueprintName = "LFLiveKitDemo"
|
||||
ReferencedContainer = "container:LFLiveKitDemo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "840762EB1D07C7D0000FD0BF"
|
||||
BuildableName = "LFLiveKitDemo.app"
|
||||
BlueprintName = "LFLiveKitDemo"
|
||||
ReferencedContainer = "container:LFLiveKitDemo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>LFLiveKitDemo.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict>
|
||||
<key>840762EB1D07C7D0000FD0BF</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>840763041D07C7D0000FD0BF</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>8407630F1D07C7D0000FD0BF</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:LFLiveKitDemo.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// AppDelegate.h
|
||||
// LFLiveKitDemo
|
||||
//
|
||||
// Created by admin on 16/6/8.
|
||||
// Copyright © 2016年 admin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// AppDelegate.m
|
||||
// LFLiveKitDemo
|
||||
//
|
||||
// Created by admin on 16/6/8.
|
||||
// Copyright © 2016年 admin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "ViewController.h"
|
||||
|
||||
@interface AppDelegate ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
// Override point for customization after application launch.
|
||||
|
||||
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
// UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]];
|
||||
// nav.navigationBarHidden = YES;
|
||||
self.window.rootViewController = [[ViewController alloc] init];
|
||||
[self.window makeKeyAndVisible];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
|
||||
}
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
}
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(UIApplication *)application {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "2x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8150" systemVersion="15A204g" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8122"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<animations/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// LFLivePreview.h
|
||||
// LFLiveKit
|
||||
//
|
||||
// Created by 倾慕 on 16/5/2.
|
||||
// Copyright © 2016年 live Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface LFLivePreview : UIView
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,333 @@
|
||||
//
|
||||
// LFLivePreview.m
|
||||
// LFLiveKit
|
||||
//
|
||||
// Created by 倾慕 on 16/5/2.
|
||||
// Copyright © 2016年 live Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LFLivePreview.h"
|
||||
#import "UIControl+YYAdd.h"
|
||||
#import "UIView+YYAdd.h"
|
||||
#import "LFLiveSession.h"
|
||||
|
||||
@interface LFLivePreview ()<LFLiveSessionDelegate>
|
||||
|
||||
@property (nonatomic, strong) UIButton *beautyButton;
|
||||
@property (nonatomic, strong) UIButton *cameraButton;
|
||||
@property (nonatomic, strong) UIButton *closeButton;
|
||||
@property (nonatomic, strong) UIButton *startLiveButton;
|
||||
@property (nonatomic, strong) UIView *containerView;
|
||||
@property (nonatomic, strong) LFLiveDebug *debugInfo;
|
||||
@property (nonatomic, strong) LFLiveSession *session;
|
||||
@property (nonatomic, strong) UILabel *stateLabel;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LFLivePreview
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame{
|
||||
if(self = [super initWithFrame:frame]){
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
[self requestAccessForVideo];
|
||||
[self requestAccessForAudio];
|
||||
[self addSubview:self.containerView];
|
||||
[self.containerView addSubview:self.stateLabel];
|
||||
[self.containerView addSubview:self.closeButton];
|
||||
[self.containerView addSubview:self.cameraButton];
|
||||
[self.containerView addSubview:self.beautyButton];
|
||||
[self.containerView addSubview:self.startLiveButton];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark -- Public Method
|
||||
- (void)requestAccessForVideo{
|
||||
__weak typeof(self) _self = self;
|
||||
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
|
||||
switch (status) {
|
||||
case AVAuthorizationStatusNotDetermined:{
|
||||
// 许可对话没有出现,发起授权许可
|
||||
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
|
||||
if (granted) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_self.session setRunning:YES];
|
||||
});
|
||||
}
|
||||
}];
|
||||
break;
|
||||
}
|
||||
case AVAuthorizationStatusAuthorized:{
|
||||
// 已经开启授权,可继续
|
||||
//dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_self.session setRunning:YES];
|
||||
//});
|
||||
break;
|
||||
}
|
||||
case AVAuthorizationStatusDenied:
|
||||
case AVAuthorizationStatusRestricted:
|
||||
// 用户明确地拒绝授权,或者相机设备无法访问
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)requestAccessForAudio{
|
||||
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
|
||||
switch (status) {
|
||||
case AVAuthorizationStatusNotDetermined:{
|
||||
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
|
||||
}];
|
||||
break;
|
||||
}
|
||||
case AVAuthorizationStatusAuthorized:{
|
||||
break;
|
||||
}
|
||||
case AVAuthorizationStatusDenied:
|
||||
case AVAuthorizationStatusRestricted:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -- LFStreamingSessionDelegate
|
||||
/** live status changed will callback */
|
||||
- (void)liveSession:(nullable LFLiveSession *)session liveStateDidChange:(LFLiveState)state{
|
||||
NSLog(@"liveStateDidChange: %ld", state);
|
||||
switch (state) {
|
||||
case LFLiveReady:
|
||||
_stateLabel.text = @"未连接";
|
||||
break;
|
||||
case LFLivePending:
|
||||
_stateLabel.text = @"连接中";
|
||||
break;
|
||||
case LFLiveStart:
|
||||
_stateLabel.text = @"已连接";
|
||||
break;
|
||||
case LFLiveError:
|
||||
_stateLabel.text = @"连接错误";
|
||||
break;
|
||||
case LFLiveStop:
|
||||
_stateLabel.text = @"未连接";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** live debug info callback */
|
||||
- (void)liveSession:(nullable LFLiveSession *)session debugInfo:(nullable LFLiveDebug*)debugInfo{
|
||||
NSLog(@"debugInfo: %lf", debugInfo.dataFlow);
|
||||
}
|
||||
|
||||
/** callback socket errorcode */
|
||||
- (void)liveSession:(nullable LFLiveSession*)session errorCode:(LFLiveSocketErrorCode)errorCode{
|
||||
NSLog(@"errorCode: %ld", errorCode);
|
||||
}
|
||||
|
||||
#pragma mark -- Getter Setter
|
||||
- (LFLiveSession*)session{
|
||||
if(!_session){
|
||||
/** 发现大家有不会用横屏的请注意啦,横屏需要在ViewController supportedInterfaceOrientations修改方向 默认竖屏 ****/
|
||||
/** 发现大家有不会用横屏的请注意啦,横屏需要在ViewController supportedInterfaceOrientations修改方向 默认竖屏 ****/
|
||||
/** 发现大家有不会用横屏的请注意啦,横屏需要在ViewController supportedInterfaceOrientations修改方向 默认竖屏 ****/
|
||||
|
||||
|
||||
|
||||
/*** 默认分辨率368 * 640 音频:44.1 iphone6以上48 双声道 方向竖屏 ***/
|
||||
|
||||
_session = [[LFLiveSession alloc] initWithAudioConfiguration:[LFLiveAudioConfiguration defaultConfiguration] videoConfiguration:[LFLiveVideoConfiguration defaultConfigurationForQuality:LFLiveVideoQuality_Medium2 landscape:NO]];
|
||||
|
||||
|
||||
/** 自己定制单声道 */
|
||||
/*
|
||||
LFLiveAudioConfiguration *audioConfiguration = [LFLiveAudioConfiguration new];
|
||||
audioConfiguration.numberOfChannels = 1;
|
||||
audioConfiguration.audioBitrate = LFLiveAudioBitRate_64Kbps;
|
||||
audioConfiguration.audioSampleRate = LFLiveAudioSampleRate_44100Hz;
|
||||
_session = [[LFLiveSession alloc] initWithAudioConfiguration:audioConfiguration videoConfiguration:[LFLiveVideoConfiguration defaultConfiguration]];
|
||||
*/
|
||||
|
||||
/** 自己定制高质量音频96K */
|
||||
/*
|
||||
LFLiveAudioConfiguration *audioConfiguration = [LFLiveAudioConfiguration new];
|
||||
audioConfiguration.numberOfChannels = 2;
|
||||
audioConfiguration.audioBitrate = LFLiveAudioBitRate_96Kbps;
|
||||
audioConfiguration.audioSampleRate = LFLiveAudioSampleRate_44100Hz;
|
||||
_session = [[LFLiveSession alloc] initWithAudioConfiguration:audioConfiguration videoConfiguration:[LFLiveVideoConfiguration defaultConfiguration]];
|
||||
*/
|
||||
|
||||
/** 自己定制高质量音频96K 分辨率设置为540*960 方向竖屏 */
|
||||
|
||||
/*
|
||||
LFLiveAudioConfiguration *audioConfiguration = [LFLiveAudioConfiguration new];
|
||||
audioConfiguration.numberOfChannels = 2;
|
||||
audioConfiguration.audioBitrate = LFLiveAudioBitRate_96Kbps;
|
||||
audioConfiguration.audioSampleRate = LFLiveAudioSampleRate_44100Hz;
|
||||
|
||||
LFLiveVideoConfiguration *videoConfiguration = [LFLiveVideoConfiguration new];
|
||||
videoConfiguration.videoSize = CGSizeMake(540, 960);
|
||||
videoConfiguration.videoBitRate = 800*1024;
|
||||
videoConfiguration.videoMaxBitRate = 1000*1024;
|
||||
videoConfiguration.videoMinBitRate = 500*1024;
|
||||
videoConfiguration.videoFrameRate = 24;
|
||||
videoConfiguration.videoMaxKeyframeInterval = 48;
|
||||
videoConfiguration.orientation = UIInterfaceOrientationPortrait;
|
||||
videoConfiguration.sessionPreset = LFCaptureSessionPreset540x960;
|
||||
|
||||
_session = [[LFLiveSession alloc] initWithAudioConfiguration:audioConfiguration videoConfiguration:videoConfiguration];
|
||||
*/
|
||||
|
||||
|
||||
/** 自己定制高质量音频128K 分辨率设置为720*1280 方向竖屏 */
|
||||
|
||||
/*
|
||||
LFLiveAudioConfiguration *audioConfiguration = [LFLiveAudioConfiguration new];
|
||||
audioConfiguration.numberOfChannels = 2;
|
||||
audioConfiguration.audioBitrate = LFLiveAudioBitRate_128Kbps;
|
||||
audioConfiguration.audioSampleRate = LFLiveAudioSampleRate_44100Hz;
|
||||
|
||||
LFLiveVideoConfiguration *videoConfiguration = [LFLiveVideoConfiguration new];
|
||||
videoConfiguration.videoSize = CGSizeMake(720, 1280);
|
||||
videoConfiguration.videoBitRate = 800*1024;
|
||||
videoConfiguration.videoMaxBitRate = 1000*1024;
|
||||
videoConfiguration.videoMinBitRate = 500*1024;
|
||||
videoConfiguration.videoFrameRate = 15;
|
||||
videoConfiguration.videoMaxKeyframeInterval = 30;
|
||||
videoConfiguration.orientation = UIInterfaceOrientationPortrait;
|
||||
videoConfiguration.sessionPreset = LFCaptureSessionPreset720x1280;
|
||||
|
||||
_session = [[LFLiveSession alloc] initWithAudioConfiguration:audioConfiguration videoConfiguration:videoConfiguration];
|
||||
*/
|
||||
|
||||
|
||||
/** 自己定制高质量音频128K 分辨率设置为720*1280 方向横屏 */
|
||||
|
||||
/*
|
||||
LFLiveAudioConfiguration *audioConfiguration = [LFLiveAudioConfiguration new];
|
||||
audioConfiguration.numberOfChannels = 2;
|
||||
audioConfiguration.audioBitrate = LFLiveAudioBitRate_128Kbps;
|
||||
audioConfiguration.audioSampleRate = LFLiveAudioSampleRate_44100Hz;
|
||||
|
||||
LFLiveVideoConfiguration *videoConfiguration = [LFLiveVideoConfiguration new];
|
||||
videoConfiguration.videoSize = CGSizeMake(1280, 720);
|
||||
videoConfiguration.videoBitRate = 800*1024;
|
||||
videoConfiguration.videoMaxBitRate = 1000*1024;
|
||||
videoConfiguration.videoMinBitRate = 500*1024;
|
||||
videoConfiguration.videoFrameRate = 15;
|
||||
videoConfiguration.videoMaxKeyframeInterval = 30;
|
||||
videoConfiguration.landscape = YES;
|
||||
videoConfiguration.sessionPreset = LFCaptureSessionPreset720x1280;
|
||||
|
||||
_session = [[LFLiveSession alloc] initWithAudioConfiguration:audioConfiguration videoConfiguration:videoConfiguration];
|
||||
*/
|
||||
|
||||
_session.delegate = self;
|
||||
_session.preView = self;
|
||||
}
|
||||
return _session;
|
||||
}
|
||||
|
||||
- (UIView*)containerView{
|
||||
if(!_containerView){
|
||||
_containerView = [UIView new];
|
||||
_containerView.frame = self.bounds;
|
||||
_containerView.backgroundColor = [UIColor clearColor];
|
||||
_containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
}
|
||||
return _containerView;
|
||||
}
|
||||
|
||||
- (UILabel*)stateLabel{
|
||||
if(!_stateLabel){
|
||||
_stateLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 80, 40)];
|
||||
_stateLabel.text = @"未连接";
|
||||
_stateLabel.textColor = [UIColor whiteColor];
|
||||
_stateLabel.font = [UIFont boldSystemFontOfSize:14.f];
|
||||
}
|
||||
return _stateLabel;
|
||||
}
|
||||
|
||||
- (UIButton*)closeButton{
|
||||
if(!_closeButton){
|
||||
_closeButton = [UIButton new];
|
||||
_closeButton.size = CGSizeMake(44, 44);
|
||||
_closeButton.left = self.width - 10 - _closeButton.width;
|
||||
_closeButton.top = 20;
|
||||
[_closeButton setImage:[UIImage imageNamed:@"close_preview"] forState:UIControlStateNormal];
|
||||
_closeButton.exclusiveTouch = YES;
|
||||
[_closeButton addBlockForControlEvents:UIControlEventTouchUpInside block:^(id sender) {
|
||||
|
||||
}];
|
||||
}
|
||||
return _closeButton;
|
||||
}
|
||||
|
||||
- (UIButton*)cameraButton{
|
||||
if(!_cameraButton){
|
||||
_cameraButton = [UIButton new];
|
||||
_cameraButton.size = CGSizeMake(44, 44);
|
||||
_cameraButton.origin = CGPointMake(_closeButton.left - 10 - _cameraButton.width, 20);
|
||||
[_cameraButton setImage:[UIImage imageNamed:@"camra_preview"] forState:UIControlStateNormal];
|
||||
_cameraButton.exclusiveTouch = YES;
|
||||
__weak typeof(self) _self = self;
|
||||
[_cameraButton addBlockForControlEvents:UIControlEventTouchUpInside block:^(id sender) {
|
||||
AVCaptureDevicePosition devicePositon = _self.session.captureDevicePosition;
|
||||
_self.session.captureDevicePosition = (devicePositon == AVCaptureDevicePositionBack) ? AVCaptureDevicePositionFront : AVCaptureDevicePositionBack;
|
||||
}];
|
||||
}
|
||||
return _cameraButton;
|
||||
}
|
||||
|
||||
- (UIButton*)beautyButton{
|
||||
if(!_beautyButton){
|
||||
_beautyButton = [UIButton new];
|
||||
_beautyButton.size = CGSizeMake(44, 44);
|
||||
_beautyButton.origin = CGPointMake(_cameraButton.left - 10 - _beautyButton.width,20);
|
||||
[_beautyButton setImage:[UIImage imageNamed:@"camra_beauty"] forState:UIControlStateSelected];
|
||||
[_beautyButton setImage:[UIImage imageNamed:@"camra_beauty_close"] forState:UIControlStateNormal];
|
||||
_beautyButton.exclusiveTouch = YES;
|
||||
__weak typeof(self) _self = self;
|
||||
[_beautyButton addBlockForControlEvents:UIControlEventTouchUpInside block:^(id sender) {
|
||||
_self.session.beautyFace = !_self.session.beautyFace;
|
||||
_self.beautyButton.selected = !_self.session.beautyFace;
|
||||
}];
|
||||
}
|
||||
return _beautyButton;
|
||||
}
|
||||
|
||||
- (UIButton*)startLiveButton{
|
||||
if(!_startLiveButton){
|
||||
_startLiveButton = [UIButton new];
|
||||
_startLiveButton.size = CGSizeMake(self.width - 60, 44);
|
||||
_startLiveButton.left = 30;
|
||||
_startLiveButton.bottom = self.height - 50;
|
||||
_startLiveButton.layer.cornerRadius = _startLiveButton.height/2;
|
||||
[_startLiveButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
|
||||
[_startLiveButton.titleLabel setFont:[UIFont systemFontOfSize:16]];
|
||||
[_startLiveButton setTitle:@"开始直播" forState:UIControlStateNormal];
|
||||
[_startLiveButton setBackgroundColor:[UIColor colorWithRed:50 green:32 blue:245 alpha:1]];
|
||||
_startLiveButton.exclusiveTouch = YES;
|
||||
__weak typeof(self) _self = self;
|
||||
[_startLiveButton addBlockForControlEvents:UIControlEventTouchUpInside block:^(id sender) {
|
||||
_self.startLiveButton.selected = !_self.startLiveButton.selected;
|
||||
if(_self.startLiveButton.selected){
|
||||
[_self.startLiveButton setTitle:@"结束直播" forState:UIControlStateNormal];
|
||||
LFLiveStreamInfo *stream = [LFLiveStreamInfo new];
|
||||
stream.url = @"rtmp://live.hkstv.hk.lxdns.com:1935/live/stream789";
|
||||
//stream.url = @"rtmp://daniulive.com:1935/live/stream2399";
|
||||
[_self.session startLive:stream];
|
||||
}else{
|
||||
[_self.startLiveButton setTitle:@"开始直播" forState:UIControlStateNormal];
|
||||
[_self.session stopLive];
|
||||
}
|
||||
}];
|
||||
}
|
||||
return _startLiveButton;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,8 @@
|
||||
source 'https://github.com/CocoaPods/Specs.git'
|
||||
platform :ios,'8.0'
|
||||
|
||||
target "LFLiveKitDemo" do
|
||||
|
||||
pod 'LFLiveKit', '~> 1.5.2'
|
||||
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// ViewController.h
|
||||
// LFLiveKitDemo
|
||||
//
|
||||
// Created by admin on 16/6/8.
|
||||
// Copyright © 2016年 admin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface ViewController : UIViewController
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// ViewController.m
|
||||
// LFLiveKitDemo
|
||||
//
|
||||
// Created by admin on 16/6/8.
|
||||
// Copyright © 2016年 admin. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ViewController.h"
|
||||
#import "LFLivePreview.h"
|
||||
|
||||
@interface ViewController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation ViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view, typically from a nib.
|
||||
[self.view addSubview:[[LFLivePreview alloc] initWithFrame:self.view.bounds]];
|
||||
}
|
||||
|
||||
- (void)didReceiveMemoryWarning {
|
||||
[super didReceiveMemoryWarning];
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
|
||||
{
|
||||
return UIInterfaceOrientationMaskPortrait;
|
||||
}
|
||||
|
||||
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// UIControl+YYAdd.h
|
||||
//
|
||||
//
|
||||
// Created by guoyaoyuan on 13-4-5.
|
||||
// Copyright (c) 2013 live Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/**
|
||||
Provides extensions for `UIControl`.
|
||||
*/
|
||||
@interface UIControl (YYAdd)
|
||||
|
||||
/**
|
||||
Removes all targets and actions for a particular event (or events)
|
||||
from an internal dispatch table.
|
||||
*/
|
||||
- (void)removeAllTargets;
|
||||
|
||||
/**
|
||||
Adds or replaces a target and action for a particular event (or events)
|
||||
to an internal dispatch table.
|
||||
|
||||
@param target The target object—that is, the object to which the
|
||||
action message is sent. If this is nil, the responder
|
||||
chain is searched for an object willing to respond to the
|
||||
action message.
|
||||
|
||||
@param action A selector identifying an action message. It cannot be NULL.
|
||||
|
||||
@param controlEvents A bitmask specifying the control events for which the
|
||||
action message is sent.
|
||||
*/
|
||||
- (void)setTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;
|
||||
|
||||
/**
|
||||
Adds a block for a particular event (or events) to an internal dispatch table.
|
||||
It will cause a strong reference to @a block.
|
||||
|
||||
@param block The block which is invoked then the action message is
|
||||
sent (cannot be nil). The block is retained.
|
||||
|
||||
@param controlEvents A bitmask specifying the control events for which the
|
||||
action message is sent.
|
||||
*/
|
||||
- (void)addBlockForControlEvents:(UIControlEvents)controlEvents block:(void (^)(id sender))block;
|
||||
|
||||
/**
|
||||
Adds or replaces a block for a particular event (or events) to an internal
|
||||
dispatch table. It will cause a strong reference to @a block.
|
||||
|
||||
@param block The block which is invoked then the action message is
|
||||
sent (cannot be nil). The block is retained.
|
||||
|
||||
@param controlEvents A bitmask specifying the control events for which the
|
||||
action message is sent.
|
||||
*/
|
||||
- (void)setBlockForControlEvents:(UIControlEvents)controlEvents block:(void (^)(id sender))block;
|
||||
|
||||
/**
|
||||
Removes all blocks for a particular event (or events) from an internal
|
||||
dispatch table.
|
||||
|
||||
@param controlEvents A bitmask specifying the control events for which the
|
||||
action message is sent.
|
||||
*/
|
||||
- (void)removeAllBlocksForControlEvents:(UIControlEvents)controlEvents;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,105 @@
|
||||
//
|
||||
// UIControl+YYAdd.m
|
||||
//
|
||||
//
|
||||
// Created by guoyaoyuan on 13-4-5.
|
||||
// Copyright (c) 2013 live Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UIControl+YYAdd.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
|
||||
static const int block_key;
|
||||
|
||||
@interface _LFUIControlBlockTarget : NSObject
|
||||
|
||||
@property (nonatomic, copy) void (^block)(id sender);
|
||||
@property (nonatomic, assign) UIControlEvents events;
|
||||
|
||||
- (id)initWithBlock:(void (^)(id sender))block events:(UIControlEvents)events;
|
||||
- (void)invoke:(id)sender;
|
||||
|
||||
@end
|
||||
|
||||
@implementation _LFUIControlBlockTarget
|
||||
|
||||
- (id)initWithBlock:(void (^)(id sender))block events:(UIControlEvents)events {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.block = block;
|
||||
self.events = events;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)invoke:(id)sender {
|
||||
if (self.block) self.block(sender);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation UIControl (LFAdd)
|
||||
|
||||
- (void)removeAllTargets {
|
||||
[[self allTargets] enumerateObjectsUsingBlock: ^(id object, BOOL *stop) {
|
||||
[self removeTarget:object
|
||||
action:NULL
|
||||
forControlEvents:UIControlEventAllEvents];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents {
|
||||
NSSet *targets = [self allTargets];
|
||||
for (id currentTarget in targets) {
|
||||
NSArray *actions = [self actionsForTarget:currentTarget forControlEvent:controlEvents];
|
||||
for (NSString *currentAction in actions) {
|
||||
[self removeTarget:currentTarget action:NSSelectorFromString(currentAction)
|
||||
forControlEvents:controlEvents];
|
||||
}
|
||||
}
|
||||
[self addTarget:target action:action forControlEvents:controlEvents];
|
||||
}
|
||||
|
||||
- (void)addBlockForControlEvents:(UIControlEvents)controlEvents
|
||||
block:(void (^)(id sender))block {
|
||||
_LFUIControlBlockTarget *target = [[_LFUIControlBlockTarget alloc]
|
||||
initWithBlock:block events:controlEvents];
|
||||
[self addTarget:target action:@selector(invoke:) forControlEvents:controlEvents];
|
||||
NSMutableArray *targets = [self _lf_allUIControlBlockTargets];
|
||||
[targets addObject:target];
|
||||
}
|
||||
|
||||
- (void)setBlockForControlEvents:(UIControlEvents)controlEvents
|
||||
block:(void (^)(id sender))block {
|
||||
[self removeAllBlocksForControlEvents:controlEvents];
|
||||
[self addBlockForControlEvents:controlEvents block:block];
|
||||
}
|
||||
|
||||
- (void)removeAllBlocksForControlEvents:(UIControlEvents)controlEvents {
|
||||
NSMutableArray *targets = [self _lf_allUIControlBlockTargets];
|
||||
NSMutableArray *removes = [NSMutableArray array];
|
||||
[targets enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
_LFUIControlBlockTarget *target = (_LFUIControlBlockTarget *)obj;
|
||||
if (target.events == controlEvents) {
|
||||
[removes addObject:target];
|
||||
[self removeTarget:target
|
||||
action:@selector(invoke:)
|
||||
forControlEvents:controlEvents];
|
||||
}
|
||||
}];
|
||||
[targets removeObjectsInArray:removes];
|
||||
}
|
||||
|
||||
- (NSMutableArray *)_lf_allUIControlBlockTargets {
|
||||
NSMutableArray *targets = objc_getAssociatedObject(self, &block_key);
|
||||
if (!targets) {
|
||||
targets = [NSMutableArray array];
|
||||
objc_setAssociatedObject(self, &block_key, targets, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// UIView+Add.h
|
||||
//
|
||||
//
|
||||
// Created by guoyaoyuan on 13-4-3.
|
||||
// Copyright (c) 2013 live Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/**
|
||||
Provides extensions for `UIView`.
|
||||
*/
|
||||
@interface UIView (YYAdd)
|
||||
|
||||
/**
|
||||
Create a snapshot image of the complete view hierarchy.
|
||||
This method should be called in main thread.
|
||||
*/
|
||||
- (UIImage *)snapshotImage;
|
||||
|
||||
/**
|
||||
Create a snapshot PDF of the complete view hierarchy.
|
||||
This method should be called in main thread.
|
||||
*/
|
||||
- (NSData *)snapshotPDF;
|
||||
|
||||
/**
|
||||
Shortcut to set the view.layer's shadow
|
||||
|
||||
@param color Shadow Color
|
||||
@param offset Shadow offset
|
||||
@param radius Shadow radius
|
||||
*/
|
||||
- (void)setLayerShadow:(UIColor*)color offset:(CGSize)offset radius:(CGFloat)radius;
|
||||
/**
|
||||
* 设置阴影 郭liyuan+
|
||||
*/
|
||||
- (void) makeInsetShadow;
|
||||
- (void) makeInsetShadowWithRadius:(float)radius Alpha:(float)alpha;
|
||||
- (void) makeInsetShadowWithRadius:(float)radius Color:(UIColor *)color Directions:(NSArray *)directions;
|
||||
|
||||
/**
|
||||
Remove all subviews.
|
||||
|
||||
@warning Never call this method inside your view's drawRect: method.
|
||||
*/
|
||||
- (void)removeAllSubviews;
|
||||
|
||||
/**
|
||||
Returns the view's view controller (may be nil).
|
||||
*/
|
||||
@property (nonatomic, readonly) UIViewController *viewController;
|
||||
|
||||
@property (nonatomic) CGFloat left; ///< Shortcut for frame.origin.x.
|
||||
@property (nonatomic) CGFloat top; ///< Shortcut for frame.origin.y
|
||||
@property (nonatomic) CGFloat right; ///< Shortcut for frame.origin.x + frame.size.width
|
||||
@property (nonatomic) CGFloat bottom; ///< Shortcut for frame.origin.y + frame.size.height
|
||||
@property (nonatomic) CGFloat width; ///< Shortcut for frame.size.width.
|
||||
@property (nonatomic) CGFloat height; ///< Shortcut for frame.size.height.
|
||||
@property (nonatomic) CGFloat centerX; ///< Shortcut for center.x
|
||||
@property (nonatomic) CGFloat centerY; ///< Shortcut for center.y
|
||||
@property (nonatomic) CGPoint origin; ///< Shortcut for frame.origin.
|
||||
@property (nonatomic) CGSize size; ///< Shortcut for frame.size.
|
||||
@property (nonatomic, readonly) CGRect screenFrame; ///< View frame on the screen, taking into account scroll views.
|
||||
|
||||
/**
|
||||
Returns the visible alpha on screen, taking into account superview and window.
|
||||
*/
|
||||
@property (nonatomic, readonly) CGFloat visibleAlpha;
|
||||
|
||||
|
||||
/**
|
||||
Converts a point from the receiver's coordinate system to that of the specified view or window.
|
||||
|
||||
@param point A point specified in the local coordinate system (bounds) of the receiver.
|
||||
@param view The view or window into whose coordinate system point is to be converted.
|
||||
If view is nil, this method instead converts to window base coordinates.
|
||||
@return The point converted to the coordinate system of view.
|
||||
*/
|
||||
- (CGPoint)convertPoint:(CGPoint)point toViewOrWindow:(UIView *)view;
|
||||
|
||||
/**
|
||||
Converts a point from the coordinate system of a given view or window to that of the receiver.
|
||||
|
||||
@param point A point specified in the local coordinate system (bounds) of view.
|
||||
@param view The view or window with point in its coordinate system.
|
||||
If view is nil, this method instead converts from window base coordinates.
|
||||
@return The point converted to the local coordinate system (bounds) of the receiver.
|
||||
*/
|
||||
- (CGPoint)convertPoint:(CGPoint)point fromViewOrWindow:(UIView *)view;
|
||||
|
||||
/**
|
||||
Converts a rectangle from the receiver's coordinate system to that of another view or window.
|
||||
|
||||
@param rect A rectangle specified in the local coordinate system (bounds) of the receiver.
|
||||
@param view The view or window that is the target of the conversion operation. If view is nil, this method instead converts to window base coordinates.
|
||||
@return The converted rectangle.
|
||||
*/
|
||||
- (CGRect)convertRect:(CGRect)rect toViewOrWindow:(UIView *)view;
|
||||
|
||||
/**
|
||||
Converts a rectangle from the coordinate system of another view or window to that of the receiver.
|
||||
|
||||
@param rect A rectangle specified in the local coordinate system (bounds) of view.
|
||||
@param view The view or window with rect in its coordinate system.
|
||||
If view is nil, this method instead converts from window base coordinates.
|
||||
@return The converted rectangle.
|
||||
*/
|
||||
- (CGRect)convertRect:(CGRect)rect fromViewOrWindow:(UIView *)view;
|
||||
|
||||
/**
|
||||
* 返回响应者链上的任意Objc
|
||||
*
|
||||
* @param viewControllerCls 需要返回的Obj的类名,为nil时默认返回当前控制器
|
||||
*
|
||||
* @return viewController Or needCls
|
||||
*/
|
||||
- (nonnull id)viewControllerWithNeedViewOrViewController:(nullable Class)viewControllerCls
|
||||
;
|
||||
|
||||
|
||||
/// 移除所有子视图中 tableview、scrollview 的 delegate、datasource
|
||||
- (void)clearScrollViewDelegate;
|
||||
|
||||
|
||||
- (void)removeAllGestures;
|
||||
- (void)removeAllGesturesWithSubViews;
|
||||
|
||||
/// 在 block 内禁用动画
|
||||
+ (void)disableAnimationWithBlock:(void (^)(void))block;
|
||||
@end
|
||||
@@ -0,0 +1,464 @@
|
||||
//
|
||||
// UIView+Add.m
|
||||
//
|
||||
//
|
||||
// Created by guoyaoyuan on 13-4-3.
|
||||
// Copyright (c) 2013 live Interactive. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UIView+YYAdd.h"
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
@implementation UIView (YYAdd)
|
||||
|
||||
- (UIImage *)snapshotImage {
|
||||
UIImage *image = nil;
|
||||
if ([self respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
|
||||
UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 0);
|
||||
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
|
||||
image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
}else{
|
||||
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, 0);
|
||||
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
|
||||
image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
- (NSData *)snapshotPDF {
|
||||
CGRect bounds = self.bounds;
|
||||
NSMutableData* data = [NSMutableData data];
|
||||
CGDataConsumerRef consumer = CGDataConsumerCreateWithCFData((__bridge CFMutableDataRef)data);
|
||||
CGContextRef context = CGPDFContextCreate(consumer, &bounds, NULL);
|
||||
CGDataConsumerRelease(consumer);
|
||||
if (!context) return nil;
|
||||
CGPDFContextBeginPage(context, NULL);
|
||||
CGContextTranslateCTM(context, 0, bounds.size.height);
|
||||
CGContextScaleCTM(context, 1.0, -1.0);
|
||||
[self.layer renderInContext:context];
|
||||
CGPDFContextEndPage(context);
|
||||
CGPDFContextClose(context);
|
||||
CGContextRelease(context);
|
||||
return data;
|
||||
}
|
||||
|
||||
- (void)setLayerShadow:(UIColor*)color offset:(CGSize)offset radius:(CGFloat)radius {
|
||||
self.layer.shadowColor = color.CGColor;
|
||||
self.layer.shadowOffset = offset;
|
||||
self.layer.shadowRadius = radius;
|
||||
self.layer.shadowOpacity = 1;
|
||||
self.layer.shouldRasterize = YES;
|
||||
self.layer.rasterizationScale = [UIScreen mainScreen].scale;
|
||||
}
|
||||
|
||||
|
||||
#define kShadowViewTag 2132
|
||||
#define kValidDirections [NSArray arrayWithObjects: @"top", @"bottom", @"left", @"right",nil]
|
||||
|
||||
- (void) makeInsetShadow
|
||||
{
|
||||
NSArray *shadowDirections = [NSArray arrayWithObjects:@"top", @"bottom", @"left" , @"right" , nil];
|
||||
UIColor *color = [UIColor colorWithRed:(0.0) green:(0.0) blue:(0.0) alpha:0.5];
|
||||
|
||||
UIView *shadowView = [self createShadowViewWithRadius:3 Color:color Directions:shadowDirections];
|
||||
shadowView.tag = kShadowViewTag;
|
||||
|
||||
[self addSubview:shadowView];
|
||||
}
|
||||
|
||||
- (void) makeInsetShadowWithRadius:(float)radius Alpha:(float)alpha
|
||||
{
|
||||
NSArray *shadowDirections = [NSArray arrayWithObjects:@"top", @"bottom", @"left" , @"right" , nil];
|
||||
UIColor *color = [UIColor colorWithRed:(0.0) green:(0.0) blue:(0.0) alpha:alpha];
|
||||
|
||||
UIView *shadowView = [self createShadowViewWithRadius:radius Color:color Directions:shadowDirections];
|
||||
shadowView.tag = kShadowViewTag;
|
||||
|
||||
[self addSubview:shadowView];
|
||||
}
|
||||
|
||||
- (void) makeInsetShadowWithRadius:(float)radius Color:(UIColor *)color Directions:(NSArray *)directions
|
||||
{
|
||||
UIView *shadowView = [self createShadowViewWithRadius:radius Color:color Directions:directions];
|
||||
shadowView.tag = kShadowViewTag;
|
||||
|
||||
[self addSubview:shadowView];
|
||||
}
|
||||
|
||||
- (UIView *) createShadowViewWithRadius:(float)radius Color:(UIColor *)color Directions:(NSArray *)directions
|
||||
{
|
||||
UIView *shadowView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)];
|
||||
shadowView.backgroundColor = [UIColor clearColor];
|
||||
|
||||
// Ignore duplicate direction
|
||||
NSMutableDictionary *directionDict = [[NSMutableDictionary alloc] init];
|
||||
for (NSString *direction in directions) [directionDict setObject:@"1" forKey:direction];
|
||||
|
||||
// for (NSString *direction in directionDict) {
|
||||
// // Ignore invalid direction
|
||||
// if ([kValidDirections containsObject:direction])
|
||||
// {
|
||||
// CAGradientLayer *shadow = [CAGradientLayer layer];
|
||||
// shadow.locations = @[@(0.0),@(0.5)];
|
||||
//
|
||||
// if ([direction isEqualToString:@"top"]) {
|
||||
// [shadow setStartPoint:CGPointMake(0.5, 0.0)];
|
||||
// [shadow setEndPoint:CGPointMake(0.5, 1.0)];
|
||||
// shadow.frame = CGRectMake(-radius, -radius, self.bounds.size.width + radius*2, radius);
|
||||
// shadow.colors = [NSArray arrayWithObjects:(id)[self.backgroundColor CGColor], (id)[color CGColor], nil];
|
||||
// }
|
||||
// else if ([direction isEqualToString:@"bottom"])
|
||||
// {
|
||||
// [shadow setStartPoint:CGPointMake(0.5, 1.0)];
|
||||
// [shadow setEndPoint:CGPointMake(0.5, 0.0)];
|
||||
// shadow.frame = CGRectMake(-radius, self.bounds.size.height, self.bounds.size.width + radius*2, radius);
|
||||
// shadow.colors = [NSArray arrayWithObjects:(id)[self.backgroundColor CGColor] ,(id)[color CGColor], nil];
|
||||
// } else if ([direction isEqualToString:@"left"])
|
||||
// {
|
||||
// shadow.frame = CGRectMake(-radius, -radius, radius, self.bounds.size.height + radius*2);
|
||||
// [shadow setStartPoint:CGPointMake(0.0, 0.5)];
|
||||
// [shadow setEndPoint:CGPointMake(1.0, 0.5)];
|
||||
// shadow.colors = [NSArray arrayWithObjects:(id)[self.backgroundColor CGColor],(id)[color CGColor], nil];
|
||||
//
|
||||
// } else if ([direction isEqualToString:@"right"])
|
||||
// {
|
||||
// shadow.frame = CGRectMake(self.bounds.size.width, -radius, radius, self.bounds.size.height + radius*2);
|
||||
// [shadow setStartPoint:CGPointMake(1.0, 0.5)];
|
||||
// [shadow setEndPoint:CGPointMake(0.0, 0.5)];
|
||||
// shadow.colors = [NSArray arrayWithObjects:(id)[self.backgroundColor CGColor],(id)[color CGColor], nil];
|
||||
// }
|
||||
// // 后边一个颜色要和所加视图背景颜色一样
|
||||
//
|
||||
// [shadowView.layer insertSublayer:shadow atIndex:0];
|
||||
// }
|
||||
// }
|
||||
|
||||
for (NSString *direction in directionDict) {
|
||||
// Ignore invalid direction
|
||||
if ([kValidDirections containsObject:direction])
|
||||
{
|
||||
CALayer *shadow = [CAGradientLayer layer];
|
||||
shadow.backgroundColor = color.CGColor;
|
||||
|
||||
if ([direction isEqualToString:@"top"]) {
|
||||
shadow.frame = CGRectMake(0, 0, self.bounds.size.width, radius);
|
||||
}
|
||||
else if ([direction isEqualToString:@"bottom"])
|
||||
{
|
||||
shadow.frame = CGRectMake(0, self.bounds.size.height, self.bounds.size.width, radius);
|
||||
} else if ([direction isEqualToString:@"left"])
|
||||
{
|
||||
shadow.frame = CGRectMake(0, 0, radius, self.bounds.size.height );
|
||||
} else if ([direction isEqualToString:@"right"])
|
||||
{
|
||||
shadow.frame = CGRectMake(self.bounds.size.width, 0, radius, self.bounds.size.height);
|
||||
}
|
||||
[shadowView.layer insertSublayer:shadow atIndex:0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return shadowView;
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (void)removeAllSubviews {
|
||||
while (self.subviews.count) {
|
||||
[self.subviews.lastObject removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (UIViewController *)viewController {
|
||||
for (UIView *view = self; view; view = view.superview) {
|
||||
UIResponder *nextResponder = [view nextResponder];
|
||||
if ([nextResponder isKindOfClass:[UIViewController class]]) {
|
||||
return (UIViewController *)nextResponder;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (nonnull id)viewControllerWithNeedViewOrViewController:(nullable Class)viewControllerCls
|
||||
{
|
||||
UIViewController *result = nil;
|
||||
|
||||
UIWindow * window = [[UIApplication sharedApplication] keyWindow];
|
||||
if (window.windowLevel != UIWindowLevelNormal)
|
||||
{
|
||||
NSArray *windows = [[UIApplication sharedApplication] windows];
|
||||
for(UIWindow * tmpWin in windows)
|
||||
{
|
||||
if (tmpWin.windowLevel == UIWindowLevelNormal)
|
||||
{
|
||||
window = tmpWin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UIView *frontView = self;
|
||||
id nextResponse = [frontView nextResponder];
|
||||
Class cls = viewControllerCls?viewControllerCls:UIViewController.class;
|
||||
while (nextResponse) {
|
||||
if ([nextResponse isKindOfClass:cls]) {
|
||||
result = nextResponse;
|
||||
break;
|
||||
}
|
||||
nextResponse = [nextResponse nextResponder];
|
||||
}
|
||||
if(!result) result = window.rootViewController;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
- (CGFloat)left {
|
||||
return self.frame.origin.x;
|
||||
}
|
||||
|
||||
- (void)setLeft:(CGFloat)x {
|
||||
CGRect frame = self.frame;
|
||||
frame.origin.x = x;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)top {
|
||||
return self.frame.origin.y;
|
||||
}
|
||||
|
||||
- (void)setTop:(CGFloat)y {
|
||||
CGRect frame = self.frame;
|
||||
frame.origin.y = y;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)right {
|
||||
return self.frame.origin.x + self.frame.size.width;
|
||||
}
|
||||
|
||||
- (void)setRight:(CGFloat)right {
|
||||
CGRect frame = self.frame;
|
||||
frame.origin.x = right - frame.size.width;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)bottom {
|
||||
return self.frame.origin.y + self.frame.size.height;
|
||||
}
|
||||
|
||||
- (void)setBottom:(CGFloat)bottom {
|
||||
CGRect frame = self.frame;
|
||||
frame.origin.y = bottom - frame.size.height;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)width {
|
||||
return self.frame.size.width;
|
||||
}
|
||||
|
||||
- (void)setWidth:(CGFloat)width {
|
||||
CGRect frame = self.frame;
|
||||
frame.size.width = width;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)height {
|
||||
return self.frame.size.height;
|
||||
}
|
||||
|
||||
- (void)setHeight:(CGFloat)height {
|
||||
CGRect frame = self.frame;
|
||||
frame.size.height = height;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)centerX {
|
||||
return self.center.x;
|
||||
}
|
||||
|
||||
- (void)setCenterX:(CGFloat)centerX {
|
||||
self.center = CGPointMake(centerX, self.center.y);
|
||||
}
|
||||
|
||||
- (CGFloat)centerY {
|
||||
return self.center.y;
|
||||
}
|
||||
|
||||
- (void)setCenterY:(CGFloat)centerY {
|
||||
self.center = CGPointMake(self.center.x, centerY);
|
||||
}
|
||||
|
||||
- (CGPoint)origin {
|
||||
return self.frame.origin;
|
||||
}
|
||||
|
||||
- (void)setOrigin:(CGPoint)origin {
|
||||
CGRect frame = self.frame;
|
||||
frame.origin = origin;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGSize)size {
|
||||
return self.frame.size;
|
||||
}
|
||||
|
||||
- (void)setSize:(CGSize)size {
|
||||
CGRect frame = self.frame;
|
||||
frame.size = size;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGRect)screenFrame {
|
||||
CGPoint origin = CGPointZero;
|
||||
for (UIView *view = self; view; view = view.superview) {
|
||||
origin.x += view.left;
|
||||
origin.y += view.top;
|
||||
|
||||
if ([view isKindOfClass:[UIScrollView class]]) {
|
||||
UIScrollView *scrollView = (UIScrollView *)view;
|
||||
origin.x -= scrollView.contentOffset.x;
|
||||
origin.y -= scrollView.contentOffset.y;
|
||||
}
|
||||
}
|
||||
return CGRectMake(origin.x, origin.y, self.width, self.height);
|
||||
}
|
||||
|
||||
|
||||
- (CGFloat)visibleAlpha {
|
||||
if ([self isKindOfClass:[UIWindow class]]) {
|
||||
if (self.hidden) return 0;
|
||||
return self.alpha;
|
||||
}
|
||||
if (!self.window) return 0;
|
||||
CGFloat alpha = 1;
|
||||
UIView *v = self;
|
||||
while (v) {
|
||||
if (v.hidden) {
|
||||
alpha = 0;
|
||||
break;
|
||||
}
|
||||
alpha *= v.alpha;
|
||||
v = v.superview;
|
||||
}
|
||||
return alpha;
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (CGPoint)convertPoint:(CGPoint)point toViewOrWindow:(UIView *)view {
|
||||
if (!view) {
|
||||
if ([self isKindOfClass:[UIWindow class]]) {
|
||||
return [((UIWindow *)self) convertPoint:point toWindow:nil];
|
||||
} else {
|
||||
return [self convertPoint:point toView:nil];
|
||||
}
|
||||
}
|
||||
|
||||
UIWindow *from = [self isKindOfClass:[UIWindow class]] ? (id)self : self.window;
|
||||
UIWindow *to = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window;
|
||||
if ((!from || !to) || (from == to)) return [self convertPoint:point toView:view];
|
||||
point = [self convertPoint:point toView:from];
|
||||
point = [to convertPoint:point fromWindow:from];
|
||||
point = [view convertPoint:point fromView:to];
|
||||
return point;
|
||||
}
|
||||
|
||||
- (CGPoint)convertPoint:(CGPoint)point fromViewOrWindow:(UIView *)view {
|
||||
if (!view) {
|
||||
if ([self isKindOfClass:[UIWindow class]]) {
|
||||
return [((UIWindow *)self) convertPoint:point fromWindow:nil];
|
||||
} else {
|
||||
return [self convertPoint:point fromView:nil];
|
||||
}
|
||||
}
|
||||
|
||||
UIWindow *from = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window;
|
||||
UIWindow *to = [self isKindOfClass:[UIWindow class]] ? (id)self : self.window;
|
||||
if ((!from || !to) || (from == to)) return [self convertPoint:point fromView:view];
|
||||
point = [from convertPoint:point fromView:view];
|
||||
point = [to convertPoint:point fromWindow:from];
|
||||
point = [self convertPoint:point fromView:to];
|
||||
return point;
|
||||
}
|
||||
|
||||
- (CGRect)convertRect:(CGRect)rect toViewOrWindow:(UIView *)view {
|
||||
if (!view) {
|
||||
if ([self isKindOfClass:[UIWindow class]]) {
|
||||
return [((UIWindow *)self) convertRect:rect toWindow:nil];
|
||||
} else {
|
||||
return [self convertRect:rect toView:nil];
|
||||
}
|
||||
}
|
||||
|
||||
UIWindow *from = [self isKindOfClass:[UIWindow class]] ? (id)self : self.window;
|
||||
UIWindow *to = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window;
|
||||
if (!from || !to) return [self convertRect:rect toView:view];
|
||||
if (from == to) return [self convertRect:rect toView:view];
|
||||
rect = [self convertRect:rect toView:from];
|
||||
rect = [to convertRect:rect fromWindow:from];
|
||||
rect = [view convertRect:rect fromView:to];
|
||||
return rect;
|
||||
}
|
||||
|
||||
- (CGRect)convertRect:(CGRect)rect fromViewOrWindow:(UIView *)view {
|
||||
if (!view) {
|
||||
if ([self isKindOfClass:[UIWindow class]]) {
|
||||
return [((UIWindow *)self) convertRect:rect fromWindow:nil];
|
||||
} else {
|
||||
return [self convertRect:rect fromView:nil];
|
||||
}
|
||||
}
|
||||
|
||||
UIWindow *from = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window;
|
||||
UIWindow *to = [self isKindOfClass:[UIWindow class]] ? (id)self : self.window;
|
||||
if ((!from || !to) || (from == to)) return [self convertRect:rect fromView:view];
|
||||
rect = [from convertRect:rect fromView:view];
|
||||
rect = [to convertRect:rect fromWindow:from];
|
||||
rect = [self convertRect:rect fromView:to];
|
||||
return rect;
|
||||
}
|
||||
|
||||
- (void)clearScrollViewDelegate {
|
||||
if ([self isKindOfClass:[UIScrollView class]]) {
|
||||
((UIScrollView *)self).delegate = nil;
|
||||
if ([self isKindOfClass:[UITableView class]]) {
|
||||
((UITableView *)self).delegate = nil;
|
||||
}
|
||||
}
|
||||
for (UIView *sub in self.subviews) {
|
||||
[sub clearScrollViewDelegate];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeAllGestures {
|
||||
NSArray *gs = [self.gestureRecognizers copy];
|
||||
for (UIGestureRecognizer *g in gs) {
|
||||
[self removeGestureRecognizer:g];
|
||||
}
|
||||
}
|
||||
- (void)removeAllGesturesWithSubViews {
|
||||
[self removeAllGestures];
|
||||
for (UIView *v in self.subviews) {
|
||||
[v removeAllGesturesWithSubViews];
|
||||
}
|
||||
[UIView animateWithDuration:0 animations:^{
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)disableAnimationWithBlock:(void (^)(void))block {
|
||||
if (!block) return;
|
||||
BOOL aniEnabled = [CATransaction disableActions];
|
||||
[CATransaction setDisableActions:YES];
|
||||
block();
|
||||
[CATransaction setDisableActions:aniEnabled];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 907 B |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// main.m
|
||||
// LFLiveKitDemo
|
||||
//
|
||||
// Created by admin on 16/6/8.
|
||||
// Copyright © 2016年 admin. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "AppDelegate.h"
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
source 'https://github.com/CocoaPods/Specs.git'
|
||||
platform :ios,'8.0'
|
||||
|
||||
target "LFLiveKitDemo" do
|
||||
|
||||
pod 'LFLiveKit', '~> 1.7.2'
|
||||
|
||||
end
|
||||