1,add AVFoundation.framework,AssetsLibrary.framework。
第一个用于获取设备摄像头,话筒,第二个保存到相册将视频。
2,
1
2
3
4
5
6
|
//apple媒体框架
#import
//C内存管理库
#import
//apple 媒体文件框架
#import
|
3,add delegate
1
2
3
|
//AVCaptureVideoDataOutputSampleBufferDelegate视频输出代理
//AVCaptureAudioDataOutputSampleBufferDelegate音频输出代理
@
interface
FourController
:
UIViewController
<
AVCaptureVideoDataOutputSampleBufferDelegate
,
AVCaptureAudioDataOutputSampleBufferDelegate
>
|
4,.h文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
//
// FourController.h
// VideoDemo
//
// Created by skyzizhu on 15/12/17.
// Copyright (c) 2015年 skyzizhu. All rights reserved.
//
#import
#import
#import
#import
@
interface
FourController
:
UIViewController
<
AVCaptureVideoDataOutputSampleBufferDelegate
,
AVCaptureAudioDataOutputSampleBufferDelegate
>
//视频输出
@
property
(
nonatomic
,
strong
)
AVCaptureVideoDataOutput *
videoOutput
;
//音频输出
@
property
(
nonatomic
,
strong
)
AVCaptureAudioDataOutput *
audioOutput
;
//当前录制session
@
property
(
nonatomic
,
strong
)
AVCaptureSession *
mediaSession
;
//视频写入文件
@
property
(
nonatomic
,
strong
)
AVAssetWriterInput *
videoWriterInput
;
//音频写入文件
@
property
(
nonatomic
,
strong
)
AVAssetWriterInput *
audioWriterInput
;
//流写入
@
property
(
nonatomic
,
strong
)
AVAssetWriter *
assetWriter
;
//单独获取视频可以用这个属性,备用
@
property
(
nonatomic
,
strong
)
AVAssetWriterInputPixelBufferAdaptor *
videoAssetWriterPixelBufferAdaptor
;
//layer
//apple提供流媒体layer,用于时时展现录制的media
@
property
(
strong
,
nonatomic
)
AVCaptureVideoPreviewLayer *
captureVideoPreviewLayer
;
//
@
end
|
5,.m文件
分别获取设备的摄像头,话筒(输入设备)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
-
(
AVCaptureDeviceInput *
)
getVideoInput
{
NSError *
error
=
nil
;
AVCaptureDevice *
device
=
[
AVCaptureDevice
defaultDeviceWithMediaType
:
AVMediaTypeVideo
]
;
AVCaptureDeviceInput *
input
=
[
AVCaptureDeviceInput
deviceInputWithDevice
:
device
error
:
&
error
]
;
return
input
;
}
-
(
AVCaptureDeviceInput *
)
getAudioInput
{
NSError *
error
=
nil
;
AVCaptureDevice *
device
=
[
AVCaptureDevice
defaultDeviceWithMediaType
:
AVMediaTypeAudio
]
;
AVCaptureDeviceInput *
input
=
[
AVCaptureDeviceInput
deviceInputWithDevice
:
device
error
:
&
error
]
;
return
input
;
}
|
分别配置视频的输出形式,配置类型等属性,这个是输出的属性,和写入的属性不同,视频和音频同事传入一个线程,两个用同一个线程就行,否则会崩溃,因为不同步。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
-
(
AVCaptureVideoDataOutput *
)
getVideoOutputWithQueue
:
(
dispatch_queue_t
)
queue
{
if
(
_videoOutput
!=
nil
)
{
return
_videoOutput
;
}
_videoOutput
=
[
[
AVCaptureVideoDataOutput
alloc
]
init
]
;
_videoOutput
.
alwaysDiscardsLateVideoFrames
=
YES
;
_videoOutput
.
videoSettings
=
[
NSDictionary
dictionaryWithObject
:
[
NSNumber
numberWithInt
:
kCVPixelFormatType_32BGRA
]
forKey
:
(
id
)
kCVPixelBufferPixelFormatTypeKey
]
;
[
_videoOutput
setSampleBufferDelegate
:
self
queue
:
queue
]
;
return
_videoOutput
;
}
-
(
AVCaptureAudioDataOutput *
)
getAudioOutputWithQueue
:
(
dispatch_queue_t
)
queue
{
if
(
_audioOutput
!=
nil
)
{
return
_audioOutput
;
}
_audioOutput
=
[
[
AVCaptureAudioDataOutput
alloc
]
init
]
;
[
_audioOutput
setSampleBufferDelegate
:
self
queue
:
queue
]
;
return
_audioOutput
;
}
|
配置当前录制session,可以看成一种会话,分别将上边设置的输入输出添加到会话里边:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
-
(
AVCaptureSession *
)
setMediaSession
{
if
(
_mediaSession
!=
nil
)
{
return
_mediaSession
;
}
_mediaSession
=
[
[
AVCaptureSession
alloc
]
init
]
;
//通知系统开始配置session
[
_mediaSession
beginConfiguration
]
;
_mediaSession
.
sessionPreset
=
AVCaptureSessionPresetLow
;
//提交当前配置
[
_mediaSession
commitConfiguration
]
;
//add input and output
[
_mediaSession
addInput
:
[
self
getVideoInput
]
]
;
[
_mediaSession
addInput
:
[
self
getAudioInput
]
]
;
//dispatch_queue_t videoQueue = dispatch_queue_create("com.videos.queue", NULL);
//传入一个线程
dispatch_queue_t
audioQueue
=
dispatch_queue_create
(
"com.audios.queue"
,
NULL
)
;
[
_mediaSession
addOutput
:
[
self
getVideoOutputWithQueue
:
audioQueue
]
]
;
[
_mediaSession
addOutput
:
[
self
getAudioOutputWithQueue
:
audioQueue
]
]
;
//开始录制
[
_mediaSession
startRunning
]
;
return
_mediaSession
;
}
|
画一个view,限制当前录制流的layer,顺便添加一个停止按钮,用于停止之后将文件保存在相册中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
-
(
void
)
setLayer
{
UIView *
v
=
[
[
UIView
alloc
]
initWithFrame
:
CGRectMake
(
50
,
100
,
280
,
400
)
]
;
[
self
.
view
addSubview
:
v
]
;
//当前流layer
_captureVideoPreviewLayer
=
[
[
AVCaptureVideoPreviewLayer
alloc
]
initWithSession
:
self
.
mediaSession
]
;
CALayer *
layer
=
v
.
layer
;
layer
.
masksToBounds
=
YES
;
_captureVideoPreviewLayer
.
frame
=
layer
.
bounds
;
//显示模式
_captureVideoPreviewLayer
.
videoGravity
=
AVLayerVideoGravityResizeAspectFill
;
[
layer
addSublayer
:
_captureVideoPreviewLayer
]
;
//
UIButton *
stop
=
[
[
UIButton
alloc
]
initWithFrame
:
CGRectMake
(
200
,
550
,
100
,
60
)
]
;
stop
.
center
=
CGPointMake
(
self
.
view
.
center
.
x
,
stop
.
center
.
y
)
;
stop
.
backgroundColor
=
[
UIColor
grayColor
]
;
[
stop
setTitle
:
@
"停止"
forState
:
UIControlStateNormal
]
;
[
stop
setTitleColor
:
[
UIColor
blackColor
]
forState
:
UIControlStateNormal
]
;
[
self
.
view
addSubview
:
stop
]
;
[
stop
addTarget
:
self
action
:
@
selector
(
stopAction
:
)
forControlEvents
:
UIControlEventTouchUpInside
]
;
}
|
配置AVAssetWriter,将视频流实时写入到文件中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
//save file path by document
-
(
NSString *
)
generateFilePathForMovie
{
return
[
NSString
stringWithFormat
:
@
"%@/play.mp4"
,
[
NSHomeDirectory
(
)
stringByAppendingPathComponent
:
@
"Documents"
]
]
;
}
-
(
AVAssetWriter *
)
setMediaWriter
{
//首先判断当前文件的路径是否存在,如果存在则删除文件,否则写入会报错
if
(
[
[
NSFileManager
defaultManager
]
fileExistsAtPath
:
[
self
generateFilePathForMovie
]
]
)
{
NSLog
(
@
"already exists"
)
;
NSError *
error
;
if
(
[
[
NSFileManager
defaultManager
]
removeItemAtPath
:
[
self
generateFilePathForMovie
]
error
:
&
error
]
==
NO
)
{
NSLog
(
@
"removeitematpath %@ error :%@"
,
[
self
generateFilePathForMovie
]
,
error
)
;
}
}
//
NSError *
error
=
nil
;
//创建AVAssetWriter,用于添加写入流的形式
//写入器传一个文件的URL,本地文件的URL,然后会自动写入。
_assetWriter
=
[
[
AVAssetWriter
alloc
]
initWithURL
:
[
NSURL
fileURLWithPath
:
[
self
generateFilePathForMovie
]
]
fileType
:
AVFileTypeQuickTimeMovie
error
:
&
error
]
;
//[_assetWriter startSessionAtSourceTime:kCMTimeZero];
//video,配置视频的写入形式。
int
bitRate
=
(
300
+
/*self.currentQuality*/
5
*
90
)
*
1024
;
//NORMAL 750 * 1024
NSDictionary *
codecSettings
=
[
NSDictionary
dictionaryWithObjectsAndKeys
:
[
NSNumber
numberWithInt
:
bitRate
]
,
AVVideoAverageBitRateKey
,
nil
]
;
//h264
NSDictionary *
videoSettings
=
[
NSDictionary
dictionaryWithObjectsAndKeys
:
AVVideoCodecH264
,
AVVideoCodecKey
,
[
NSNumber
numberWithInt
:
480
]
,
AVVideoWidthKey
,
[
NSNumber
numberWithInt
:
320
]
,
AVVideoHeightKey
,
codecSettings
,
AVVideoCompressionPropertiesKey
,
nil
]
;
_videoWriterInput
=
[
AVAssetWriterInput
assetWriterInputWithMediaType
:
AVMediaTypeVideo
outputSettings
:
videoSettings
]
;
_videoWriterInput
.
expectsMediaDataInRealTime
=
YES
;
/*这个注释的是单独写入视频用这个。
self.videoAssetWriterPixelBufferAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc] initWithAssetWriterInput:_videoWriterInput sourcePixelBufferAttributes:
[NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA],kCVPixelBufferPixelFormatTypeKey, nil]];
*/
//audio
// Add the audio input
//配置音频,aac
AudioChannelLayout
acl
;
bzero
(
&
acl
,
sizeof
(
acl
)
)
;
acl
.
mChannelLayoutTag
=
kAudioChannelLayoutTag_Mono
;
NSDictionary*
audioOutputSettings
=
nil
;
// Both type of audio inputs causes output video file to be corrupted.
if
(
/* DISABLES CODE */
(
NO
)
)
{
// should work from iphone 3GS on and from ipod 3rd generation
audioOutputSettings
=
[
NSDictionary
dictionaryWithObjectsAndKeys
:
[
NSNumber
numberWithInt
:
kAudioFormatMPEG4AAC
]
,
AVFormatIDKey
,
[
NSNumber
numberWithInt
:
1
]
,
AVNumberOfChannelsKey
,
[
NSNumber
numberWithFloat
:
44100.0
]
,
AVSampleRateKey
,
[
NSNumber
numberWithInt
:
64000
]
,
AVEncoderBitRateKey
,
[
NSData
dataWithBytes
:
&
acl
length
:
sizeof
(
acl
)
]
,
AVChannelLayoutKey
,
nil
]
;
}
else
{
// should work on any device requires more space
audioOutputSettings
=
[
NSDictionary
dictionaryWithObjectsAndKeys
:
[
NSNumber
numberWithInt
:
kAudioFormatAppleLossless
]
,
AVFormatIDKey
,
[
NSNumber
numberWithInt
:
16
]
,
AVEncoderBitDepthHintKey
,
[
NSNumber
numberWithFloat
:
44100.0
]
,
AVSampleRateKey
,
[
NSNumber
numberWithInt
:
1
]
,
AVNumberOfChannelsKey
,
[
NSData
dataWithBytes
:
&
acl
length
:
sizeof
(
acl
)
]
,
AVChannelLayoutKey
,
nil
]
;
}
_audioWriterInput
=
[
AVAssetWriterInput
assetWriterInputWithMediaType
:
AVMediaTypeAudio
outputSettings
:
audioOutputSettings
]
;
//视频和音频的expectsMediaDataInRealTime属性必须是yes,这样才能获取实时数据
_audioWriterInput
.
expectsMediaDataInRealTime
=
YES
;
//将视频写入和音频写入加入到媒体写入器里边
[
_assetWriter
addInput
:
_videoWriterInput
]
;
[
_assetWriter
addInput
:
_audioWriterInput
]
;
return
_assetWriter
;
}
|
获取实时的代理方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
-
(
void
)
captureOutput
:
(
AVCaptureOutput *
)
captureOutput
didOutputSampleBuffer
:
(
CMSampleBufferRef
)
sampleBuffer
fromConnection
:
(
AVCaptureConnection *
)
connection
{
//sampleBuffer是实时流,转换成data,查看大小
NSData *
data
=
[
NSData
dataWithBytes
:
&
sampleBuffer
length
:
malloc_size
(
sampleBuffer
)
]
;
NSLog
(
@
"%ld"
,
data
.
length
)
;
if
(
!
CMSampleBufferDataIsReady
(
sampleBuffer
)
)
{
NSLog
(
@
"sample buffer is not ready. Skipping sample"
)
;
return
;
}
//设置写入器的写入时间,开启写入
if
(
_assetWriter
.
status
==
AVAssetWriterStatusUnknown
)
{
CMTime
startTime
=
CMSampleBufferGetPresentationTimeStamp
(
sampleBuffer
)
;
[
_assetWriter
startWriting
]
;
[
_assetWriter
startSessionAtSourceTime
:
startTime
]
;
}
if
(
_assetWriter
.
status
==
AVAssetWriterStatusFailed
)
{
NSLog
(
@
"error - %@"
,
_assetWriter
.
error
)
;
}
//判断如果正在读取,则直接写入
if
(
_assetWriter
.
status
==
AVAssetWriterStatusWriting
)
{
//写入视频
if
(
[
captureOutput
isKindOfClass
:
[
_audioOutput
class
]
]
)
{
[
_audioWriterInput
appendSampleBuffer
:
sampleBuffer
]
;
}
//写入音频
if
(
[
captureOutput
isKindOfClass
:
[
_videoOutput
class
]
]
)
{
[
_videoWriterInput
appendSampleBuffer
:
sampleBuffer
]
;
}
}
|
button event
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
#pragma mark - button action
-
(
void
)
stopAction
:
(
UIButton *
)
bt
{
//停止录制
[
_mediaSession
stopRunning
]
;
//写入器写入完成调用的方法
[
_assetWriter
finishWritingWithCompletionHandler
:
^
{
NSString *
filePath
=
[
self
generateFilePathForMovie
]
;
NSData *
data
=
[
NSData
dataWithContentsOfFile
:
filePath
]
;
NSLog
(
@
"%@"
,
filePath
)
;
NSLog
(
@
"data = = = = %ld"
,
data
.
length
)
;
//写入到相册
[
self
saveMedia
:
filePath
]
;
}
]
;
}
#pragma mark - save media
-
(
void
)
saveMedia
:
(
NSString*
)
urlString
{
ALAssetsLibrary *
library
=
[
[
ALAssetsLibrary
alloc
]
init
]
;
[
library
writeVideoAtPathToSavedPhotosAlbum
:
[
NSURL
fileURLWithPath
:
urlString
]
completionBlock
:
^
(
NSURL *
assetURL
,
NSError *
error
)
{
NSLog
(
@
"%@"
,
assetURL
)
;
if
(
error
&&
assetURL
==
nil
)
{
NSLog
(
@
"Save video fail:%@"
,
error
)
;
}
else
{
NSLog
(
@
"Save video succeed."
)
;
if
(
[
[
NSFileManager
defaultManager
]
fileExistsAtPath
:
[
self
generateFilePathForMovie
]
]
)
{
NSError *
error
;
if
(
[
[
NSFileManager
defaultManager
]
removeItemAtPath
:
[
self
generateFilePathForMovie
]
error
:
&
error
]
==
NO
)
{
NSLog
(
@
"removeitematpath %@ error :%@"
,
[
self
generateFilePathForMovie
]
,
error
)
;
}
}
}
}
]
;
}
|
调用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
-
(
void
)
viewDidLoad
{
[
super
viewDidLoad
]
;
//write
//先配置写入,然后录制,否则两个不在同一个线程,导致崩溃
[
self
setMediaWriter
]
;
//session
[
self
setMediaSession
]
;
//add media layer
[
self
setLayer
]
;
// Do any additional setup after loading the view.
}
|
延伸:
如果单独写入视频,则用AVAssetWriterInputPixelBufferAdaptor类型就可以写入,在代理方法里边:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#pragma mark - avcapturevideo delegate
-
(
void
)
captureOutput
:
(
AVCaptureOutput *
)
captureOutput
didOutputSampleBuffer
:
(
CMSampleBufferRef
)
sampleBuffer
fromConnection
:
(
AVCaptureConnection *
)
connection
{
//write
//转换成imageBuffer
CVImageBufferRef
imageBuffer
=
CMSampleBufferGetImageBuffer
(
sampleBuffer
)
;
// a very dense way to keep track of the time at which this frame
// occurs relative to the output stream, but it's just an example!
//CFRelease(sampleBuffer);
//通过assetWriterPixelBufferAdaptor属性写入视频
static
int64_t
frameNumber
=
0
;
if
(
self
.
assetWriterInput
.
readyForMoreMediaData
)
[
self
.
assetWriterPixelBufferAdaptor
appendPixelBuffer
:
imageBuffer
withPresentationTime
:
CMTimeMake
(
frameNumber
,
14
)
]
;
frameNumber
++
;
/*/
//
/*
NSData *data = [NSData dataWithBytes:&sampleBuffer length:malloc_size(sampleBuffer)];
NSLog(@"temp - - %ld",data.length);
[self recieveVideoFromData:data];
*/
}
|
如果不想用系统的实时layer,则可以将转换的data转换成iamge,通过切换iamgeview的图片实时播放视频:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
//将buffer转换的data转换成image,放在view上
-
(
void
)
recieveVideoFromData
:
(
NSData *
)
data
{
CMSampleBufferRef
sampleBuffer
;
[
data
getBytes
:
&
sampleBuffer
length
:
sizeof
(
sampleBuffer
)
]
;
CVImageBufferRef
imageBuffer
=
CMSampleBufferGetImageBuffer
(
sampleBuffer
)
;
CVPixelBufferLockBaseAddress
(
imageBuffer
,
0
)
;
uint8_t *
baseAddress
=
(
uint8_t *
)
CVPixelBufferGetBaseAddress
(
imageBuffer
)
;
size_t
bytesPerRow
=
CVPixelBufferGetBytesPerRow
(
imageBuffer
)
;
size_t
width
=
CVPixelBufferGetWidth
(
imageBuffer
)
;
size_t
height
=
CVPixelBufferGetHeight
(
imageBuffer
)
;
CGColorSpaceRef
colorSpace
=
CGColorSpaceCreateDeviceRGB
(
)
;
CGContextRef
newContext
=
CGBitmapContextCreate
(
baseAddress
,
width
,
height
,
8
,
bytesPerRow
,
colorSpace
,
kCGBitmapByteOrder32Little
|
kCGImageAlphaPremultipliedFirst
)
;
CGImageRef
newImage
=
CGBitmapContextCreateImage
(
newContext
)
;
CGContextRelease
(
newContext
)
;
CGColorSpaceRelease
(
colorSpace
)
;
UIImage *
image
=
[
UIImage
imageWithCGImage
:
newImage
scale
:
1.0
orientation
:
UIImageOrientationRight
]
;
CGImageRelease
(
newImage
)
;
[
self
.
imageView
performSelectorOnMainThread
:
@
selector
(
setImage
:
)
withObject
:
image
waitUntilDone
:
YES
]
;
CVPixelBufferUnlockBaseAddress
(
imageBuffer
,
0
)
;
}
|
ios设备获取自身IP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
//add #include
+
(
NSString*
)
getIPAddress
{
NSString*
address
;
struct
ifaddrs *
interfaces
=
nil
;
// get all our interfaces and find the one that corresponds to wifi
if
(
!
getifaddrs
(
&
interfaces
)
)
{
for
(
struct
ifaddrs*
addr
=
interfaces
;
addr
!=
NULL
;
addr
=
addr
->
ifa_next
)
{
if
(
(
[
[
NSString
stringWithUTF8String
:
addr
->
ifa_name
]
isEqualToString
:
@
"en0"
]
)
&&
(
addr
->
ifa_addr
->
sa_family
==
AF_INET
)
)
{
struct
sockaddr_in*
sa
=
(
struct
sockaddr_in*
)
addr
->
ifa_addr
;
address
=
[
NSString
stringWithUTF8String
:
inet_ntoa
(
sa
->
sin_addr
)
]
;
break
;
}
}
}
freeifaddrs
(
interfaces
)
;
return
address
;
}
|