Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dca255052b | |||
| b51c53e93b | |||
| 21763f5c1d |
+3
-3
@@ -2,15 +2,15 @@
|
||||
Pod::Spec.new do |s|
|
||||
|
||||
s.name = "LFLiveKit"
|
||||
s.version = "1.4.1"
|
||||
s.summary = "objectc ios Live. LFLiveKit."
|
||||
s.version = "1.5"
|
||||
s.summary = "LaiFeng ios Live. LFLiveKit."
|
||||
s.homepage = "https://github.com/chenliming777"
|
||||
s.license = { :type => "MIT", :file => "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/**/*.{h,m}"
|
||||
s.source_files = "LFLiveKit/**/*.{*}"
|
||||
s.public_header_files = "LFLiveKit/**/*.h"
|
||||
|
||||
s.frameworks = "VideoToolbox", "AudioToolbox","AVFoundation","Foundation","UIKit"
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "5CFA1E6A87A325722DE7DCED3A58CCA1"
|
||||
BlueprintIdentifier = "7C2BDDF89D4243C7BCA44592D146DEED"
|
||||
BuildableName = "libPods-LFLiveKit.a"
|
||||
BlueprintName = "Pods-LFLiveKit"
|
||||
ReferencedContainer = "container:Pods/Pods.xcodeproj">
|
||||
|
||||
BIN
Binary file not shown.
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.4.1</string>
|
||||
<string>1.5</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -17,6 +17,13 @@
|
||||
|
||||
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>
|
||||
@@ -82,7 +89,7 @@ typedef void (^ LFRequestComplete)(_Nullable id info,NSError *_Nullable errorMsg
|
||||
The designated initializer. Multiple instances with the same configuration will make the
|
||||
capture unstable.
|
||||
*/
|
||||
- (nullable instancetype)initWithAudioConfiguration:(nullable LFLiveAudioConfiguration*)audioConfiguration videoConfiguration:(nullable LFLiveVideoConfiguration*)videoConfiguration NS_DESIGNATED_INITIALIZER;
|
||||
- (nullable instancetype)initWithAudioConfiguration:(nullable LFLiveAudioConfiguration*)audioConfiguration videoConfiguration:(nullable LFLiveVideoConfiguration*)videoConfiguration liveType:(LFLiveType)liveType NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/** The start stream .*/
|
||||
- (void)startLive:(nonnull LFLiveStreamInfo*)streamInfo;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#import "LFHardwareVideoEncoder.h"
|
||||
#import "LFHardwareAudioEncoder.h"
|
||||
#import "LFStreamRtmpSocket.h"
|
||||
#import "LFStreamTcpSocket.h"
|
||||
#import "LFLiveStreamInfo.h"
|
||||
|
||||
#define LFLiveReportKey @"com.youku.liveSessionReport"
|
||||
@@ -20,6 +21,8 @@
|
||||
{
|
||||
dispatch_semaphore_t _lock;
|
||||
}
|
||||
///流媒体格式
|
||||
@property (nonatomic, assign) LFLiveType liveType;
|
||||
///音频配置
|
||||
@property (nonatomic, strong) LFLiveAudioConfiguration *audioConfiguration;
|
||||
///视频配置
|
||||
@@ -62,11 +65,12 @@
|
||||
@implementation LFLiveSession
|
||||
|
||||
#pragma mark -- LifeCycle
|
||||
- (instancetype)initWithAudioConfiguration:(LFLiveAudioConfiguration *)audioConfiguration videoConfiguration:(LFLiveVideoConfiguration *)videoConfiguration{
|
||||
- (instancetype)initWithAudioConfiguration:(LFLiveAudioConfiguration *)audioConfiguration videoConfiguration:(LFLiveVideoConfiguration *)videoConfiguration liveType:(LFLiveType)liveType{
|
||||
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;
|
||||
@@ -235,7 +239,11 @@
|
||||
|
||||
- (id<LFStreamSocket>)socket{
|
||||
if(!_socket){
|
||||
_socket = [[LFStreamRtmpSocket alloc] initWithStream:self.streamInfo];
|
||||
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 setDelegate:self];
|
||||
}
|
||||
return _socket;
|
||||
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// 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
|
||||
Executable
+345
@@ -0,0 +1,345 @@
|
||||
//
|
||||
// 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
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
Executable
+1166
File diff suppressed because it is too large
Load Diff
Executable
+231
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
$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__ */
|
||||
Executable
+298
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
$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;
|
||||
}
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
$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__ */
|
||||
Executable
+498
@@ -0,0 +1,498 @@
|
||||
/*
|
||||
$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;
|
||||
}
|
||||
Executable
+202
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
$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__ */
|
||||
Executable
+626
@@ -0,0 +1,626 @@
|
||||
/*
|
||||
$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);
|
||||
}
|
||||
|
||||
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
$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__ */
|
||||
Executable
+92
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
$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 */
|
||||
Executable
+141
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
$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,19 @@
|
||||
//
|
||||
// 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
|
||||
@@ -0,0 +1,331 @@
|
||||
//
|
||||
// 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
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
[](https://travis-ci.org/chenliming777/LFLiveKit)
|
||||
[](https://travis-ci.org/LaiFengiOS/LFLiveKit)
|
||||
[](https://raw.githubusercontent.com/chenliming777/LFLiveKit/master/LICENSE)
|
||||
[](http://cocoapods.org/?q=LFLiveKit)
|
||||
[](https://www.apple.com/nl/ios/)
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
LFLiveKit
|
||||
|
||||
LFLiveKit IOS mobile phone push code,Default format support RTMP,At the same time, the structure is very easy to extend.
|
||||
LFLiveKit IOS mobile phone push code,Default format support RTMP and FLV,At the same time, the structure is very easy to extend.
|
||||
|
||||
Podfile
|
||||
To integrate LFLiveKit into your Xcode project using CocoaPods, specify it in your Podfile:
|
||||
@@ -43,13 +43,13 @@ Architecture
|
||||
|
||||
capture: LFAudioCapture and LFVideoCapture
|
||||
encode: LFHardwareAudioEncoder and LFHardwareVideoEncoder
|
||||
publish: LFStreamRtmpSocket
|
||||
publish: LFStreamRtmpSocket LFStreamTcpSocket
|
||||
|
||||
Usage
|
||||
|
||||
- (LFLiveSession*)session{
|
||||
if(!_session){
|
||||
_session = [[LFLiveSession alloc] initWithAudioConfiguration: [LFLiveAudioConfiguration defaultConfiguration] videoConfiguration: [LFLiveVideoConfiguration defaultConfiguration]];
|
||||
_session = [[LFLiveSession alloc] initWithAudioConfiguration:[LFLiveAudioConfiguration defaultConfiguration] videoConfiguration:[LFLiveVideoConfiguration defaultConfiguration] liveType:LFLiveRTMP];
|
||||
_session.running = YES;
|
||||
_session.preView = self;
|
||||
}
|
||||
@@ -73,7 +73,7 @@ Usage
|
||||
videoConfiguration.orientation = UIInterfaceOrientationLandscapeLeft;
|
||||
videoConfiguration.sessionPreset = LFCaptureSessionPreset720x1280;
|
||||
|
||||
_session = [[LFLiveSession alloc] initWithAudioConfiguration:audioConfiguration videoConfiguration:videoConfiguration];
|
||||
_session = [[LFLiveSession alloc] initWithAudioConfiguration:audioConfiguration videoConfiguration:videoConfiguration liveType:LFLiveRTMP];
|
||||
_session.running = YES;
|
||||
_session.preView = self;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user