Blog

清风细雨的博客

View the Project on GitHub cwos111509sina/Blog

iOS 视频的录制、合成以及播放

(觉得有用可以给个星星)

根据项目要求,视频可以暂停然后继续录制。选择了视频合成。后面有链接

导入AVFoundation库文件用于支持:

AVCaptureSession 链接输入输出设备
AVCaptureDeviceInput 从设备获取输入设备
AVCaptureMovieFileOutput 视频输出
AVCaptureVideoPreviewLayer 录制预览layer

AVMutableComposition 根据MediaType返回音视频轨道
AVMutableVideoComposition 设施输出内容属性,大小,帧数。。。
AVAssetTrack 素材通道
AVURLAsset 获取音视频信息
AVAssetExportSession 输出视频

导入MediaPlayer库用于支持:
MPMoviePlayerController 视频播放控件

info.plist文件需要添加下面内容获取相应权限
Privacy - Camera Usage Description (是否允许此App使用你的相机?)
Privacy - Microphone Usage Description (是否允许此App使用你的麦克风?)

视频合成过程需要时间,这里没做处理,可以根据自己需要添加一个UIActivityIndicatorView



//
//  ViewController.m
//  VideoRecordText
//
//

#import "ViewController.h"


#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>

#define WIDTH [UIScreen mainScreen].bounds.size.width
#define HEIGHT [UIScreen mainScreen].bounds.size.height


@interface ViewController ()<AVCaptureFileOutputRecordingDelegate>

@property (strong,nonatomic) AVCaptureSession *captureSession;//负责输入和输出设置之间的数据传递
@property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput;//负责从AVCaptureDevice获得输入数据
@property (strong,nonatomic) AVCaptureMovieFileOutput *captureMovieFileOutput;//视频输出流
@property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;//相机拍摄预览图层

@property (nonatomic,strong)NSString * fileName;//文件夹名
@property (nonatomic,assign)int fileNum;//文件名
@property (nonatomic,strong)NSMutableArray * fileUrlArr;//文件路径数组


@property (nonatomic,strong)MPMoviePlayerController * mediaV;//播放器

@property (nonatomic,strong)UIView * playBcgV;//播放页面

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    _fileUrlArr = [[NSMutableArray alloc]init];
    _fileName = [NSString stringWithFormat:@"%d",(int)[[NSDate dateWithTimeIntervalSinceNow:0] timeIntervalSince1970]];
    _fileNum = 0;

    [self createPlayBcgV];//播放视图

    [self createUI];

    // Do any additional setup after loading the view, typically from a nib.
}

-(void)createUI{


    UIView * layerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, WIDTH, HEIGHT-140)];

    layerView.backgroundColor = [UIColor blackColor];


    //初始化会话
    _captureSession=[[AVCaptureSession alloc]init];
    [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
        if (granted) {
        }else{
            NSLog(@"未获取摄像头权限");
        }
    }];
    //获得输入设备(视频)
    AVCaptureDevice *captureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置摄像头
    if (!captureDevice) {
        NSLog(@"取得后置摄像头时出现问题.");
        return;
    }

    NSError *error=nil;
    //根据输入设备初始化设备输入对象,用于获得输入数据
    _captureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];
    if (error) {
        NSLog(@"取得视频设备输入对象时出错,错误原因:%@",error.localizedDescription);
        return;
    }
    //添加一个输入设备(音频)
    AVCaptureDevice *audioCaptureDevice=[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];

    AVCaptureDeviceInput *audioCaptureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:audioCaptureDevice error:&error];
    if (error) {
        NSLog(@"取得音频设备输入对象时出错,错误原因:%@",error.localizedDescription);
        return;
    }
    //初始化设备输出对象,用于获得输出数据
    _captureMovieFileOutput=[[AVCaptureMovieFileOutput alloc]init];

    //将设备输入添加到会话中
    if ([_captureSession canAddInput:_captureDeviceInput]) {
        [_captureSession addInput:_captureDeviceInput];
        [_captureSession addInput:audioCaptureDeviceInput];
        AVCaptureConnection *captureConnection=[_captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
        if ([captureConnection isVideoStabilizationSupported ]) {
            captureConnection.preferredVideoStabilizationMode=AVCaptureVideoStabilizationModeAuto;
        }
    }

    //将设备输出添加到会话中
    if ([_captureSession canAddOutput:_captureMovieFileOutput]) {
        [_captureSession addOutput:_captureMovieFileOutput];
    }
    //创建视频预览层,用于实时展示摄像头状态
    _captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];

    _captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;

    CALayer *layer= layerView.layer;
    layer.masksToBounds=YES;
    _captureVideoPreviewLayer.frame = CGRectMake(0, 0, WIDTH, HEIGHT);

    //将视频预览层添加到界面中
    [layer addSublayer:_captureVideoPreviewLayer];


    [self.captureSession startRunning];


    [self.view addSubview:layerView];

    [self createButton];
}


#pragma mark --------------------清空按钮-------------------
-(void)clearBtnClick:(UIButton *)btn{
    NSLog(@"清空");

    UIButton * button = (UIButton *)[self.view viewWithTag:100];

    if (button.selected) {
        return;
    }

    NSFileManager* fileManager=[NSFileManager defaultManager];

    NSString *pathDocuments = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *createPath = [NSString stringWithFormat:@"%@/myVidio", pathDocuments];

    // 判断文件夹是否存在,如果存在,清空
    if ([[NSFileManager defaultManager] fileExistsAtPath:createPath]) {
        [fileManager removeItemAtPath:createPath error:nil];
    }

}

#pragma mark --------------------视频录制-------------------
-(void)recordStartOrPause:(UIButton *)btn{

    btn.selected = !btn.selected;

    if (btn.selected) {//点击开始方法
        NSLog(@"录制");

        NSString * pathDocument = [self checkPath];
        [_fileUrlArr addObject:pathDocument];

        [_captureMovieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:pathDocument] recordingDelegate:self];

        _fileNum ++;

    }else{//停止方法
        NSLog(@"停止");

         [_captureMovieFileOutput stopRecording];
    }

}

-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error{

    NSLog(@"录制完成");

}

#pragma mark --------------------录制完成-------------------
-(void)finishBtnClick:(UIButton *)btn{//点击进行视频合成操作并跳转到PlayViewController
    UIButton * button = (UIButton *)[self.view viewWithTag:100];

    if (button.selected) {
        return;

    }
    NSLog(@"完成");

    if (_fileUrlArr.count < 1) {
        return;
    }
    //合成后视频出处路径
    NSString *pathDocuments = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *createPath = [NSString stringWithFormat:@"%@/myVidio/%@/%@.mp4", pathDocuments,_fileName,_fileName];



    if (_fileUrlArr.count > 1) {

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];

            AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];

            AVMutableCompositionTrack *audioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio
                                                                                preferredTrackID:kCMPersistentTrackID_Invalid];
            AVMutableCompositionTrack *videoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo
                                                                                preferredTrackID:kCMPersistentTrackID_Invalid];

            CMTime totalDuration = kCMTimeZero;

            for (int i = 0; i < _fileUrlArr.count; i++) {
                AVURLAsset *asset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:_fileUrlArr[i]]];
                NSError *erroraudio = nil;//获取AVAsset中的音频 或者视频
                AVAssetTrack *assetAudioTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] firstObject];//向通道内加入音频或者视频
                //                BOOL ba =
                [audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration)
                                    ofTrack:assetAudioTrack
                                     atTime:totalDuration
                                      error:&erroraudio];

                //        NSLog(@"erroraudio:%@%d",erroraudio,ba);
                NSError *errorVideo = nil;
                AVAssetTrack *assetVideoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo]firstObject];

                //                BOOL bl =
                [videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration)
                                    ofTrack:assetVideoTrack
                                     atTime:totalDuration
                                      error:&errorVideo];

                //        NSLog(@"errorVideo:%@%d",errorVideo,bl);
                totalDuration = CMTimeAdd(totalDuration, asset.duration);

                videoComposition.frameDuration = CMTimeMake(1, 30);
                //视频输出尺寸
                videoComposition.renderSize = CGSizeMake(assetVideoTrack.naturalSize.height,assetVideoTrack.naturalSize.height*(HEIGHT/(HEIGHT-140)));


                AVMutableVideoCompositionInstruction * avMutableVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];

                [avMutableVideoCompositionInstruction setTimeRange:CMTimeRangeMake(kCMTimeZero, [mixComposition duration])];

                AVMutableVideoCompositionLayerInstruction * avMutableVideoCompositionLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:assetAudioTrack];

                [avMutableVideoCompositionLayerInstruction setTransform:assetVideoTrack.preferredTransform atTime:kCMTimeZero];

                avMutableVideoCompositionInstruction.layerInstructions = [NSArray arrayWithObject:avMutableVideoCompositionLayerInstruction];

                videoComposition.instructions = [NSArray arrayWithObject:avMutableVideoCompositionInstruction];


            }

            NSFileManager* fileManager=[NSFileManager defaultManager];
            BOOL blHave=[[NSFileManager defaultManager] fileExistsAtPath:createPath];
            if (blHave) {
                [fileManager removeItemAtPath:createPath error:nil];
            }


            NSURL *mergeFileURL = [NSURL fileURLWithPath:createPath];
            //        NSLog(@"starvideorecordVC: 345 outpath = %@",outpath);
            AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetMediumQuality];
            exporter.outputURL = mergeFileURL;

            exporter.videoComposition = videoComposition;
            exporter.outputFileType = AVFileTypeMPEG4;
            exporter.shouldOptimizeForNetworkUse = YES;
            [exporter exportAsynchronouslyWithCompletionHandler:^{
                NSLog(@" exporter%@",exporter.error);
                if (exporter.status == AVAssetExportSessionStatusCompleted) {

                    dispatch_async(dispatch_get_main_queue(), ^{

                        _playBcgV.hidden = NO;

                        [self.view bringSubviewToFront:_playBcgV];

                        _mediaV.contentURL = mergeFileURL;

                        [_mediaV prepareToPlay];
                        [_mediaV play];


                    });
                }
            }];


        });
    }else{

        _playBcgV.hidden = NO;

        [self.view bringSubviewToFront:_playBcgV];

        _mediaV.contentURL = [NSURL fileURLWithPath:_fileUrlArr[0]];


        [_mediaV prepareToPlay];
        [_mediaV play];
    }

}



#pragma mark - -----------------获取摄像头设备---------------------

/**
 *  取得指定位置的摄像头
 *
 *  @param position 摄像头位置
 *
 *  @return 摄像头设备
 */
-(AVCaptureDevice *)getCameraDeviceWithPosition:(AVCaptureDevicePosition )position{

    NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *camera in cameras) {
        if ([camera position]==position) {
            return camera;
        }
    }
    return nil;
}

#pragma mark --------------------检查文件路径------------------
-(NSString *)checkPath{
    NSFileManager* fileManager=[NSFileManager defaultManager];

    NSString *pathDocuments = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *createPath = [NSString stringWithFormat:@"%@/myVidio/%@", pathDocuments,_fileName];

    // 判断文件夹是否存在,如果不存在,则创建
    if (![[NSFileManager defaultManager] fileExistsAtPath:createPath]) {
        [fileManager createDirectoryAtPath:createPath withIntermediateDirectories:YES attributes:nil error:nil];
    }

    NSString *pathDocument = [NSString stringWithFormat:@"%@/%d.mp4",createPath,_fileNum];

    BOOL blHave=[[NSFileManager defaultManager] fileExistsAtPath:pathDocument];
    if (blHave) {
        [fileManager removeItemAtPath:pathDocument error:nil];
    }

    return pathDocument;

}



#pragma mark ---------------------创建控制按钮----------------------
-(void)createButton{

    UIButton * recordBtn = [UIButton buttonWithType:UIButtonTypeCustom];

    recordBtn.frame = CGRectMake(30, HEIGHT-100, WIDTH/3-40, 40);

    [recordBtn setTitle:@"开始" forState:UIControlStateNormal];
    [recordBtn setTitle:@"停止" forState:UIControlStateSelected];
    recordBtn.tag = 100;
    recordBtn.backgroundColor = [UIColor blackColor];

    recordBtn.layer.cornerRadius = 20;
    recordBtn.clipsToBounds = YES;


    [recordBtn addTarget:self action:@selector(recordStartOrPause:) forControlEvents:UIControlEventTouchUpInside];

    [self.view addSubview:recordBtn];


    UIButton * finishBtn = [UIButton buttonWithType:UIButtonTypeCustom];

    finishBtn.frame = CGRectMake(WIDTH/3+20, HEIGHT-100, WIDTH/3-40, 40);

    [finishBtn setTitle:@"完成" forState:UIControlStateNormal];
    finishBtn.backgroundColor = [UIColor blackColor];

    finishBtn.layer.cornerRadius = 20;
    finishBtn.clipsToBounds = YES;
    [finishBtn addTarget:self action:@selector(finishBtnClick:) forControlEvents:UIControlEventTouchUpInside];

    [self.view addSubview:finishBtn];



    UIButton * clearBtn = [UIButton buttonWithType:UIButtonTypeCustom];

    clearBtn.frame = CGRectMake(WIDTH/3 * 2 + 10, HEIGHT-100, WIDTH/3-40, 40);
    [clearBtn setTitle:@"清空" forState:UIControlStateNormal];
    clearBtn.backgroundColor = [UIColor blackColor];

    clearBtn.layer.cornerRadius = 20;
    clearBtn.clipsToBounds = YES;
    [clearBtn addTarget:self action:@selector(clearBtnClick:) forControlEvents:UIControlEventTouchUpInside];

    [self.view addSubview:clearBtn];


}

#pragma mark ---------------------创建播放视图----------------------

-(void)createPlayBcgV{
    _playBcgV = [[UIView alloc]initWithFrame:CGRectMake(0, 0, WIDTH, HEIGHT)];

    _playBcgV.backgroundColor = [UIColor blackColor];


    _mediaV = [[MPMoviePlayerController alloc]init];

    _mediaV.view.frame = CGRectMake(0, 0, WIDTH, HEIGHT-120);
    _mediaV.view.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;

    [_playBcgV addSubview:_mediaV.view];



    UIButton * hideBtn = [UIButton buttonWithType:UIButtonTypeCustom];

    hideBtn.frame = CGRectMake(30, HEIGHT-100, WIDTH-60, 40);

    [hideBtn setTitle:@"隐藏" forState:UIControlStateNormal];

    hideBtn.backgroundColor = [UIColor whiteColor];
    [hideBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    hideBtn.layer.cornerRadius = 20;
    hideBtn.clipsToBounds = YES;

    [hideBtn addTarget:self action:@selector(hideBtnClick:) forControlEvents:UIControlEventTouchUpInside];

    [_playBcgV addSubview:hideBtn];


    _playBcgV.hidden = YES;

    [self.view addSubview:_playBcgV];

}
#pragma mark ---------------------隐藏播放控制器---------------------
-(void)hideBtnClick:(UIButton *)btn{
    NSLog(@"隐藏");
    [_mediaV stop];
    _playBcgV.hidden = YES;

}

@end

项目地址

返回首页