调用系统相机录像,压缩保存到相册(附仿微信视频录制demo)
2016-07-29 14:31
921 查看
使用系统相机录像,使用的AVFoundation框架。首先了解一下框架的使用。
一、录制的相关类有:
1、AVCaptureSession
媒体(音、视频)捕获会话,负责把捕获的音视频数据输出到输出设备中。一个AVCaptureSession可以有多个输入输出。
2、AVCaptureDevice
输入设备,包括麦克风、摄像头,通过该对象可以设置物理设备的一些属性(例如相机聚焦、白平衡等)。
3、AVCaptureDeviceInput
设备输入数据管理对象,可以根据AVCaptureDevice创建对应AVCaptureDeviceInput对象,该对象将会被添加到AVCaptureSession中管理。
4、AVCaptureOutput
输出数据管理对象,用于接收各类输出数据,通常使用对应的子类AVCaptureAudioDataOutput、AVCaptureStillImageOutput、AVCaptureVideoDataOutput、AVCaptureFileOutput,该对象将会被添加到AVCaptureSession中管理。注意:前面几个对象的输出数据都是NSData类型,而AVCaptureFileOutput代表数据以文件形式输出,类似的,AVCcaptureFileOutput也不会直接创建使用,通常会使用其子类:AVCaptureAudioFileOutput、AVCaptureMovieFileOutput。当把一个输入或者输出添加到AVCaptureSession之后AVCaptureSession就会在所有相符的输入、输出设备之间建立连接(AVCaptionConnection)。
5、AVCaptureVideoPreviewLayer
相机拍摄预览图层,是CALayer的子类,使用该对象可以实时查看拍照或视频录制效果,创建该对象需要指定对应的AVCaptureSession对象。
二、使用AVFoundation拍照和录制视频的一般步骤如下:
1、创建AVCaptureSession对象。
2、使用AVCaptureDevice的静态方法获得需要使用的设备,例如拍照和录像就需要获得摄像头设备,录音就要获得麦克风设备。
3、利用输入设备AVCaptureDevice初始化AVCaptureDeviceInput对象。
4、初始化输出数据管理对象,如果要拍照就初始化AVCaptureStillImageOutput对象;如果拍摄视频就初始化AVCaptureMovieFileOutput对象。
5、将数据输入对象AVCaptureDeviceInput、数据输出对象AVCaptureOutput添加到媒体会话管理对象AVCaptureSession中。
6、创建视频预览图层AVCaptureVideoPreviewLayer并指定媒体会话,添加图层到显示容器中,调用AVCaptureSession的startRuning方法开始捕获。
7、将捕获的音频或视频数据输出到指定文件。
介绍完录制视频,要介绍一下播放器的使用。使用的也是AVFoundation框架中的类是AVPlayer。
AVPlayer本身并不能显示视频,而且它也不像MPMoviePlayerController有一个view属性。如果AVPlayer要显示必须创建一个播放器层AVPlayerLayer用于展示,播放器层继承于CALayer,有了AVPlayerLayer之添加到控制器视图的layer中即可。要使用AVPlayer首先了解一下几个常用的类:
1、AVAsset:主要用于获取多媒体信息,是一个抽象类,不能直接使用。
2、AVURLAsset:AVAsset的子类,可以根据一个URL路径创建一个包含媒体信息的AVURLAsset对象。
3、AVPlayerItem:一个媒体资源管理对象,管理者视频的一些基本信息和状态,一个AVPlayerItem对应着一个视频资源。
简单介绍AVFoundation框架之后,放上一个自己写的小demo,类似于微信的视频录制。能保存到相册。效果图如下:
具体代码如下:
ViewController.m中:
附上demo下载地址:http://download.csdn.net/detail/qq_34195670/9589592
github下载地址:https://github.com/goingmyway1/RecordingVideoDemo
以上如有错误,请留言指正,谢谢!
一、录制的相关类有:
1、AVCaptureSession
媒体(音、视频)捕获会话,负责把捕获的音视频数据输出到输出设备中。一个AVCaptureSession可以有多个输入输出。
2、AVCaptureDevice
输入设备,包括麦克风、摄像头,通过该对象可以设置物理设备的一些属性(例如相机聚焦、白平衡等)。
3、AVCaptureDeviceInput
设备输入数据管理对象,可以根据AVCaptureDevice创建对应AVCaptureDeviceInput对象,该对象将会被添加到AVCaptureSession中管理。
4、AVCaptureOutput
输出数据管理对象,用于接收各类输出数据,通常使用对应的子类AVCaptureAudioDataOutput、AVCaptureStillImageOutput、AVCaptureVideoDataOutput、AVCaptureFileOutput,该对象将会被添加到AVCaptureSession中管理。注意:前面几个对象的输出数据都是NSData类型,而AVCaptureFileOutput代表数据以文件形式输出,类似的,AVCcaptureFileOutput也不会直接创建使用,通常会使用其子类:AVCaptureAudioFileOutput、AVCaptureMovieFileOutput。当把一个输入或者输出添加到AVCaptureSession之后AVCaptureSession就会在所有相符的输入、输出设备之间建立连接(AVCaptionConnection)。
5、AVCaptureVideoPreviewLayer
相机拍摄预览图层,是CALayer的子类,使用该对象可以实时查看拍照或视频录制效果,创建该对象需要指定对应的AVCaptureSession对象。
二、使用AVFoundation拍照和录制视频的一般步骤如下:
1、创建AVCaptureSession对象。
2、使用AVCaptureDevice的静态方法获得需要使用的设备,例如拍照和录像就需要获得摄像头设备,录音就要获得麦克风设备。
3、利用输入设备AVCaptureDevice初始化AVCaptureDeviceInput对象。
4、初始化输出数据管理对象,如果要拍照就初始化AVCaptureStillImageOutput对象;如果拍摄视频就初始化AVCaptureMovieFileOutput对象。
5、将数据输入对象AVCaptureDeviceInput、数据输出对象AVCaptureOutput添加到媒体会话管理对象AVCaptureSession中。
6、创建视频预览图层AVCaptureVideoPreviewLayer并指定媒体会话,添加图层到显示容器中,调用AVCaptureSession的startRuning方法开始捕获。
7、将捕获的音频或视频数据输出到指定文件。
介绍完录制视频,要介绍一下播放器的使用。使用的也是AVFoundation框架中的类是AVPlayer。
AVPlayer本身并不能显示视频,而且它也不像MPMoviePlayerController有一个view属性。如果AVPlayer要显示必须创建一个播放器层AVPlayerLayer用于展示,播放器层继承于CALayer,有了AVPlayerLayer之添加到控制器视图的layer中即可。要使用AVPlayer首先了解一下几个常用的类:
1、AVAsset:主要用于获取多媒体信息,是一个抽象类,不能直接使用。
2、AVURLAsset:AVAsset的子类,可以根据一个URL路径创建一个包含媒体信息的AVURLAsset对象。
3、AVPlayerItem:一个媒体资源管理对象,管理者视频的一些基本信息和状态,一个AVPlayerItem对应着一个视频资源。
简单介绍AVFoundation框架之后,放上一个自己写的小demo,类似于微信的视频录制。能保存到相册。效果图如下:
具体代码如下:
ViewController.m中:
#import "ViewController.h" #import <AVFoundation/AVFoundation.h> #import <AVKit/AVKit.h> #import <AssetsLibrary/AssetsLibrary.h> #import "UserDefine.h" typedef NS_ENUM(NSInteger,VideoStatus){ VideoStatusEnded = 0, VideoStatusStarted }; @interface ViewController ()<AVCaptureFileOutputRecordingDelegate> { //拍摄视频相关 AVCaptureSession * _captureSession;/**< 是一个会话对象,是设备音频/视频整个录制期间的管理者. */ AVCaptureDevice *_videoDevice;/**< 视频设备 */ AVCaptureDevice *_audioDevice;/**< 音频设备 */ AVCaptureDeviceInput *_videoInput;/**< 视频输入 */ AVCaptureDeviceInput *_audioInput;/**< 音频输入 */ AVCaptureMovieFileOutput *_movieOutput;/**< 视频输出 */ AVCaptureVideoPreviewLayer *_captureVideoPreviewLayer;/**< 预览拍摄过程中的图像 */ //播放相关 AVPlayer *_player;/**< 播放器对象 */ AVPlayerItem *_playItem;/**< 一个媒体资源管理对象,管理者视频的一些基本信息和状态,一个AVPlayerItem对应着一个视频资源 */ AVPlayerLayer *_playerLayer; BOOL _isPlaying; } @property (strong, nonatomic) UIView *recordingView;/**< */ @property (strong, nonatomic) UIButton *recordingButton;/**< 录制按钮 */ @property (strong, nonatomic) UILabel *timeLabel;/**< 倒计时时间 */ @property (strong, nonatomic) UIButton *playButton;/**< 播放按钮 */ @property (nonatomic,assign) VideoStatus status; @property (nonatomic,assign) BOOL canSave; @property (nonatomic,strong) CADisplayLink *link; @property (strong, nonatomic) NSURL *videoUrl;/**< 视频URL */ @end @implementation ViewController static float CountdownTime = 15 * 60;//倒计时时间,时间为15s,* 60 因为CADisplayLink 1/60秒刷新一次 - (void)viewDidLoad { [super viewDidLoad]; [self setUILayout]; [self getAuthorization]; //对视频播放完进行监听 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:)name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; } -(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; } -(void)viewDidDisappear:(BOOL)animated{ [super viewDidDisappear:animated]; //移除通知 [[NSNotificationCenter defaultCenter]removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; } #pragma mark - **************** 界面布局 -(void)setUILayout{ self.view.backgroundColor = RGBColor(245, 245, 245); CGFloat jianGe = 20; CGFloat btnH = 30; CGFloat btnW = 120; CGFloat lblH = 40; CGFloat lblW = 120; CGFloat boFangH = 160; _recordingView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_W, SCREEN_H/2)]; _recordingView.backgroundColor = [UIColor blackColor]; [self.view addSubview:_recordingView]; _timeLabel = [[UILabel alloc]initWithFrame:CGRectMake(self.view.center.x - lblW/2, _recordingView.frame.size.height + _recordingView.frame.origin.y + lblH, lblW, lblH)]; _timeLabel.hidden = NO; _timeLabel.text = [NSString stringWithFormat:@"倒计时:%d秒",(int)(CountdownTime/60)]; _timeLabel.textAlignment = NSTextAlignmentCenter; _timeLabel.font = [UIFont systemFontOfSize:18.0]; _timeLabel.textColor = RGBColor(217, 28, 26); [self.view addSubview:_timeLabel]; _recordingButton = [UIButton buttonWithType:UIButtonTypeCustom]; _recordingButton.frame = CGRectMake(self.view.center.x - btnW/2, _timeLabel.frame.size.height + _timeLabel.frame.origin.y + jianGe, btnW, btnH); [_recordingButton setTitle:@"开始录制" forState:UIControlStateNormal]; [_recordingButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; _recordingButton.backgroundColor = RGBColor(217, 28, 26); _recordingButton.titleLabel.font = [UIFont fontWithName:@"STHeitiSC-Light" size:14.0]; _recordingButton.layer.cornerRadius = 3; [_recordingButton addTarget:self action:@selector(recordButton:) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:_recordingButton]; _playButton = [UIButton buttonWithType:UIButtonTypeCustom]; _playButton.frame = CGRectMake(0, 0, boFangH, boFangH); _playButton.center = _recordingView.center; [_playButton setImage:[UIImage imageNamed:@"MMVideoPreviewPlay"] forState:UIControlStateNormal]; _playButton.hidden = YES; [_playButton addTarget:self action:@selector(playButton:) forControlEvents:UIControlEventTouchUpInside]; [_recordingView addSubview:_playButton]; } #pragma mark - **************** Button 方法 /** * 点击录制 * * @param sender */ -(void)recordButton:(UIButton *)sender{ if ([sender.titleLabel.text isEqualToString:@"开始录制"]) { [self startAnimation]; }else{ [self saveVideo:_videoUrl]; } } /** * 点击播放 * * @param sender */ -(void)playButton:(UIButton *)sender{ [_player play]; _playButton.hidden = YES; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } #pragma mark - **************** 视频录制相关 #pragma mark -- 获取授权 -(void)getAuthorization{ //此处获取摄像头授权 switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) { case AVAuthorizationStatusAuthorized://已授权可以使用 { NSLog(@"授权成功!"); [self setupAVCaptureInfo]; } break; case AVAuthorizationStatusNotDetermined://未授权 { //则再次请求授权 [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { if (granted) {//授权成功 [self setupAVCaptureInfo]; return; }else{ //授权失败 return; } }]; } break; default: //用户拒绝授权/未授权 break; } } #pragma mark - 设置相关信息 -(void)setupAVCaptureInfo{ [self addSession]; //开始配置视频的会话对象 [_captureSession beginConfiguration]; [self addVideo]; [self addAudio]; [self addPreviewLayer]; //提交配置 [_captureSession commitConfiguration]; //开启会话----> 不等于开始录制 [_captureSession startRunning]; } /** * 设置视频的会话对象 */ -(void)addSession{ _captureSession = [[AVCaptureSession alloc]init]; //设置视频分辨率 //注意,这个地方设置的模式/分辨率大小将影响后面的拍摄质量 if ([_captureSession canSetSessionPreset:AVAssetExportPreset640x480]) { [_captureSession setSessionPreset:AVAssetExportPreset640x480]; } } /** * 设置视频设备 */ -(void)addVideo{ _videoDevice = [self deviceWithMediaType:AVMediaTypeVideo preferringPosition:AVCaptureDevicePositionBack];//AVCaptureDevicePositionBack -- 后摄像头 [self addVideoInput]; [self addMovieOutput]; } /** * 设置视频输入对象 */ -(void)addVideoInput{ NSError *videoError; //视频输入对象 //根据输入设备初始化输入对象,用户获取输入数据 _videoInput = [[AVCaptureDeviceInput alloc]initWithDevice:_videoDevice error:&videoError]; if (videoError) { NSLog(@"-------取得摄像头设备时出错---%@",[videoError localizedDescription]); return; } //将视频输入对象添加到会话(AVCaptureSession)中 if ([_captureSession canAddInput:_videoInput]) { [_captureSession addInput:_videoInput]; } } /** * 设置视频输出对象 */ -(void)addMovieOutput{ //拍摄视频输出对象 //初始化输出设备对象,用户获取输出数据 _movieOutput = [[AVCaptureMovieFileOutput alloc] init]; if ([_captureSession canAddOutput:_movieOutput]) { [_captureSession addOutput:_movieOutput]; //设置连接管理对象 AVCaptureConnection *captureConnection = [_movieOutput connectionWithMediaType:AVMediaTypeVideo]; //视频稳定设置 if ([captureConnection isVideoStabilizationSupported]) { captureConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto; } //视频旋转方向的设置 captureConnection.videoScaleAndCropFactor = captureConnection.videoMaxScaleAndCropFactor; } } /** * 设置音频设备 */ -(void)addAudio{ NSError *audioError; //添加一个音频设备 _audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; // 音频输入对象 _audioInput = [[AVCaptureDeviceInput alloc]initWithDevice:_audioDevice error:&audioError]; if (audioError) { NSLog(@"取得录音设备时出错 ------ %@",audioError); return; } //将音频输入对象添加到会话 (AVCaptureSession) 中 if ([_captureSession canAddInput:_audioInput]) { [_captureSession addInput:_audioInput]; } } /** * 设置预览层 */ -(void)addPreviewLayer{ [self.view layoutIfNeeded]; //通过会话(AVCaptureSession)创建预览图层 _captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:_captureSession]; _captureVideoPreviewLayer.frame = self.view.layer.bounds; //如果预览图层和视频方向不一致,可以修改这个 _captureVideoPreviewLayer.connection.videoOrientation = [_movieOutput connectionWithMediaType:AVMediaTypeVideo].videoOrientation; //设置captureVideoPreviewLayer在父视图中的位置 _captureVideoPreviewLayer.position = CGPointMake(self.view.frame.size.width*0.5,self.recordingView.frame.size.height*0.5); //显示在视图表面的图层 CALayer *layer = self.recordingView.layer; layer.masksToBounds = YES; [self.view layoutIfNeeded]; [layer addSublayer:_captureVideoPreviewLayer]; } -(void)startAnimation{ if (self.status == VideoStatusEnded) { self.status = VideoStatusStarted; [UIView animateWithDuration:0.5 animations:^{ } completion:^(BOOL finished) { [self stopLink]; [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; }]; } } -(void)stopAnimation{ if (self.status == VideoStatusStarted) { self.status = VideoStatusEnded; [self stopLink]; [self stopRecord]; _playButton.hidden = NO; [UIView animateWithDuration:0.5 animations:^{ [_recordingButton setTitle:@"保存到相册" forState:UIControlStateNormal]; } completion:^(BOOL finished) { }]; } } -(CADisplayLink *)link{ if (!_link) { _link = [CADisplayLink displayLinkWithTarget:self selector:@selector(refresh:)]; [self startRecord]; } return _link; } -(void)stopLink{ _link.paused = YES; [_link invalidate]; _link = nil; } -(void)refresh:(CADisplayLink *)link{ if (CountdownTime <= 0) { CountdownTime = 15 * 60; [self recordComplete]; [self stopAnimation]; _timeLabel.hidden = YES; return; } CountdownTime -= 1; NSLog(@"%f",CountdownTime); _timeLabel.text = [NSString stringWithFormat:@"倒计时:%d秒",(int)(CountdownTime/60)]; } /** * 获取录制视频的地址 * * @return outPutFileURL */ - (NSURL *)outPutFileURL { return [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), @"outPut.mov"]]; } - (void)startRecord { [_movieOutput startRecordingToOutputFileURL:[self outPutFileURL] recordingDelegate:self]; } - (void)stopRecord { // 取消视频拍摄 [_movieOutput stopRecording]; } - (void)recordComplete { self.canSave = YES; } //这个在完全退出小视频时调用 - (void)quit { [_captureSession stopRunning]; } - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections { NSLog(@"---- 开始录制 ----"); } - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error { NSLog(@"---- 录制结束 ---%@-%@ ",outputFileURL,captureOutput.outputFileURL); if (outputFileURL.absoluteString.length == 0 && captureOutput.outputFileURL.absoluteString.length == 0 ) { return; } if (self.canSave) { _videoUrl = outputFileURL; self.canSave = NO; [self creatPlayView]; } } #pragma mark - 获取摄像头-->前/后 -(AVCaptureDevice *)deviceWithMediaType:(NSString *)mediaType preferringPosition:(AVCaptureDevicePosition)position{ NSArray *devices = [AVCaptureDevice devicesWithMediaType:mediaType]; AVCaptureDevice *captureDevice = devices.firstObject; for (AVCaptureDevice *device in devices) { if (device.position == position) { captureDevice = device; break; } } return captureDevice; } #pragma mark - **************** 播放相关 -(void)creatPlayView{ NSLog(@"%@",_videoUrl); [_captureVideoPreviewLayer removeFromSuperlayer]; [self.view layoutIfNeeded]; _playItem = [AVPlayerItem playerItemWithURL:self.videoUrl]; _player = [AVPlayer playerWithPlayerItem:_playItem]; _playerLayer =[AVPlayerLayer playerLayerWithPlayer:_player]; _playerLayer.frame = _recordingView.frame; _playerLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//视频填充模式 _playerLayer.position = CGPointMake(self.view.frame.size.width*0.5,self.recordingView.frame.size.height*0.5); CALayer *layer = self.recordingView.layer; layer.masksToBounds = true; [self.view layoutIfNeeded]; [layer addSublayer:_playerLayer]; [self.recordingView bringSubviewToFront:_playButton]; } #pragma mark - 视频播放通知回调 -(void)playbackFinished:(NSNotification *)notification { [_player seekToTime:CMTimeMake(0, 1)]; _playButton.hidden = NO; } #pragma mark - **************** 压缩保存 - (NSURL *)compressedURL { return [NSURL fileURLWithPath:[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) lastObject] stringByAppendingPathComponent:[NSString stringWithFormat:@"compressed.mp4"]]]; } - (CGFloat)fileSize:(NSURL *)path { return [[NSData dataWithContentsOfURL:path] length]/1024.00 /1024.00; } // 压缩视频 -(void)saveVideoWithUrl:(NSURL *)url { NSLog(@"开始压缩,压缩前大小 %f MB",[self fileSize:url]); AVURLAsset *avAsset = [[AVURLAsset alloc] initWithURL:url options:nil]; NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset]; if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) { AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPreset640x480]; exportSession.outputURL = [self compressedURL]; //优化网络 exportSession.shouldOptimizeForNetworkUse = true; //转换后的格式 exportSession.outputFileType = AVFileTypeMPEG4; //异步导出 [exportSession exportAsynchronouslyWithCompletionHandler:^{ // 如果导出的状态为完成 if ([exportSession status] == AVAssetExportSessionStatusCompleted) { NSLog(@"压缩完毕,压缩后大小 %f MB",[self fileSize:[self compressedURL]]); [self saveVideo:[self compressedURL]]; }else{ NSLog(@"当前压缩进度:%f",exportSession.progress); } }]; } } - (void)saveVideo:(NSURL *)outputFileURL { ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; [library writeVideoAtPathToSavedPhotosAlbum:outputFileURL completionBlock:^(NSURL *assetURL, NSError *error) { if (error) { NSLog(@"保存视频失败:%@",error); } else { NSLog(@"保存视频到相册成功"); } }]; } @end
附上demo下载地址:http://download.csdn.net/detail/qq_34195670/9589592
github下载地址:https://github.com/goingmyway1/RecordingVideoDemo
以上如有错误,请留言指正,谢谢!
相关文章推荐
- Swift - 视频录制教程1(调用摄像头录像,并保存到系统相册)
- android调用系统相机进行视频录制并保存到指定目录
- 很全的demo,包括调用系统相机全屏拍照并保存,访问系统相册,多项保存照片。
- Android调用系统相机拍照、从相册中取照片,裁剪,并保存到手机SD卡中,展示到界面
- 调用系统相机拍照、调用相册、调用系统录像并获取缩略图
- 调用系统相机相册录像机demo
- 简单的调用系统摄像头和相机保存到Sd卡的Demo
- 完美调用系统相机拍照,获取图片进行压缩并本地保存
- 调用系统图片库选择图片,调用摄像头拍摄图片保存图片,调用摄像头录像保存录像视频片段
- Android 调用系统相机拍照保存以及调用系统相册的方法
- 调用相机 实现:视频录制,拍照 保存 上传服务器 UIImagePickerController AVPlayer
- Android 调用系统相机拍照保存以及调用系统相册的方法
- 调用系统UIImagePickerController录像并保存到指定的文件夹,解决保存后播放视频角度偏移90度问题
- 调用系统相机和系统相册,并保存到沙盒中
- Android拍照调用系统相册仿微信封装总结,治疗各种崩溃,图片横竖问题压缩等问题。
- Android 调用系统相机拍照保存以及调用系统相册的方法
- Android拍照调用系统相册仿微信封装总结,治疗各种崩溃,图片横竖问题压缩等问题。-更新2016-12-21
- Android开发基础 调用相机 系统相册(并对图片进行压缩处理)
- Android 调用系统相机拍照保存以及调用系统相册的方法
- 调用系统相机拍照,并且保存到系统相册的一般套路