零基础 Object-C 学习路线推荐 : Object-C 学习目录 >> Object-C 基础
零基础 Object-C 学习路线推荐 : Object-C 学习目录 >> Object-C 线程
零基础 Object-C 学习路线推荐 : Object-C 学习目录 >> OpenGL ES
零基础 Object-C 学习路线推荐 : Object-C 学习目录 >> GPUImage
零基础 Object-C 学习路线推荐 : Object-C 学习目录 >> AVFoundation
零基础 Object-C 学习路线推荐 : Object-C 学习目录 >> CocoaPods
一.相关API简介
1.AVURLAsset
AVAsset 是 AVFoundation 框架中的核心的类,它提供了基于时间的音视频数据.(如电影文件,视频流),一个 asset 包含很多轨道的结合,如 audio , video , text , closed captions, subtitles …
AVAsset : 主要用于获取多媒体信息,是一个抽象类,不能直接使用。
AVURLAsset : AVAsset 的子类,可以根据一个 URL 路径创建一个包含媒体信息的 AVURLAsset 对象;
2.AVAssetTrack
根据 AVAsset 获取 AVAssetTrack 检查视频是否自带旋转角度(比如相机拍摄的视频有可能带有旋转角度)
/******************************************************************************************/
//@Author:猿说编程
//@Blog(个人博客地址): www.codersrc.com
//@File:AVFoundation - AVAssetExportSession 导出视频到沙盒目录
//@Time:2021/08/08 07:30
//@Motto:不积跬步无以至千里,不积小流无以成江海,程序人生的精彩需要坚持不懈地积累!
/******************************************************************************************/
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
//加载视频文件,检查是否有旋转角度
NSURL* url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"123.mp4" ofType:nil]];
AVURLAsset* asset = [[AVURLAsset alloc] initWithURL:url options:nil];
NSLog(@"time:%f",CMTimeGetSeconds(asset.duration));
NSLog(@"angle : %d",[self degressFromVideoFileWithURL:url]);
//加载音频文件,检查是否有视频
NSURL* url2 = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"456.mp3" ofType:nil]];
AVURLAsset* asset2 = [[AVURLAsset alloc] initWithURL:url options:nil];
NSArray *videoTracks = [asset2 tracksWithMediaType:AVMediaTypeVideo]; // 检查是否有视频轨道
NSArray *audioTracks = [asset2 tracksWithMediaType:AVMediaTypeAudio]; // 检查是否有音频轨道
if([videoTracks count] > 0)
NSLog(@"存在视频");
if([audioTracks count] > 0)
NSLog(@"存在音频");
}
-(NSUInteger)degressFromVideoFileWithURL:(NSURL *)url
{
NSUInteger degress = 0;
AVAsset *asset = [AVAsset assetWithURL:url];
NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; // 检查是否有视频轨道
if([tracks count] > 0) {
AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
//获取视频轨道相关信息
CGAffineTransform t = videoTrack.preferredTransform;
if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0){
// Portrait
degress = 90;
}else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0){
// PortraitUpsideDown
degress = 270;
}else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0){
// LandscapeRight
degress = 0;
}else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0){
// LandscapeLeft
degress = 180;
}
}
return degress;
}
/*
time:4249.883000
angle : 90
存在音频
*/
3.AVComposition/AVMutableComposition
AVFoundation 框架中提供了丰富的接口用于视听资源的编辑,其中的关键是 composition ,它将不同的 AVAsset 相结合并形成一个新的 AVAsset 。
使用 AVMutableComposition 类可以增删 AVAsset 来将单个或者多个 AVAsset 集合到一起。除此之外,若想将集合到一起的视听资源以自定义的方式进行播放,需要使用 AVMutableAudioMix 和 AVMutableVideoComposition 类对其中的资源进行协调管理。
4.AVMutableVideoComposition
AVFoundation 类 API 中最核心的类是 AVVideoComposition / AVMutableVideoComposition 。
AVVideoComposition / AVMutableVideoComposition 对两个或多个视频轨道组合在一起的方法给出了一个总体描述。它由一组时间范围和描述组合行为的介绍内容组成。这些信息出现在组合资源内的任意时间点。
AVVideoComposition / AVMutableVideoComposition 管理所有视频轨道,可以决定最终视频的尺寸,裁剪需要在这里进行;
5.AVMutableCompositionTrack
将多个 AVAsset 集合到一起合成新视频中轨道信息,有音频轨、视频轨等,里面可以插入各种对应的素材(画中画,水印等);
6.AVMutableVideoCompositionLayerInstruction
AVMutableVideoCompositionLayerInstruction 主要用于对视频轨道中的一个视频处理缩放、模糊、裁剪、旋转等;
7.AVMutableVideoCompositionInstruction
表示一个指令,决定一个 timeRange 内每个轨道的状态,每一个指令包含多个 AVMutableVideoCompositionLayerInstruction ;而 AVVideoComposition 由多个 AVVideoCompositionInstruction 构成;
AVVideoCompositionInstruction 所提供的最关键的一段数据是组合对象时间轴内的时间范围信息。这一时间范围是在某一组合形式出现时的时间范围。要执行的组全特质是通过其 AVMutableVideoCompositionLayerInstruction 集合定义的。
8.AVAssetExportSession
AVAssetExportSession 主要用于导出视频到本地;也是 AVFoundation 框架中非常重要的函数之一;
二.导出视频到沙盒实战
为了避免导出失败,解决方案是提取视频资源 AVURLAsset 音频轨道和视频轨道,重新构建 AVComposition ,然后在使用 AVAssetExportSession 导出,代码如下:
/******************************************************************************************/
//@Author:猿说编程
//@Blog(个人博客地址): www.codersrc.com
//@File:AVFoundation - AVAssetExportSession 导出视频到沙盒目录
//@Time:2021/08/08 07:30
//@Motto:不积跬步无以至千里,不积小流无以成江海,程序人生的精彩需要坚持不懈地积累!
/******************************************************************************************/
#pragma mark - 获取视频角度
- (int)degressFromVideoFileWithAsset:(AVAsset *)asset {
int degress = 0;
NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
if([tracks count] > 0) {
AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
CGAffineTransform t = videoTrack.preferredTransform;
if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0){
// Portrait
degress = 90;
} else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0){
// PortraitUpsideDown
degress = 270;
} else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0){
// LandscapeRight
degress = 0;
} else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0){
// LandscapeLeft
degress = 180;
}
}
return degress;
}
#pragma mark - 导出
- (void)startExportMP4VideoWithVideoAsset:(AVURLAsset *)videoAsset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
AVMutableComposition *composition = [AVMutableComposition composition];
AVMutableCompositionTrack *compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *compositionAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
//获取视频资源中视频轨道
AVAssetTrack *sourceVideoTrack = [videoAsset tracksWithMediaType:AVMediaTypeVideo].firstObject;
//获取视频资源中音频轨道
AVAssetTrack *sourceAudioTrack = [videoAsset tracksWithMediaType:AVMediaTypeAudio].firstObject;
//视频插入的时间点
[compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration) ofTrack:sourceVideoTrack atTime:kCMTimeZero error:nil];
//音频插入的时间点
[compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration) ofTrack:sourceAudioTrack atTime:kCMTimeZero error:nil];
//presetNam
NSArray *presets = [AVAssetExportSession exportPresetsCompatibleWithAsset:composition];
if ([presets containsObject:presetName]) {
AVAssetExportSession *session = [[AVAssetExportSession alloc] initWithAsset:composition presetName:presetName];
// 输出地址
NSDateFormatter *formater = [[NSDateFormatter alloc] init];
[formater setDateFormat:@"yyyy-MM-dd-HH:mm:ss-SSS"];
NSString *outputPath = [NSHomeDirectory() stringByAppendingFormat:@"/tmp/video-%@.mp4", [formater stringFromDate:[NSDate date]]];
session.outputURL = [NSURL fileURLWithPath:outputPath];
// 优化
session.shouldOptimizeForNetworkUse = YES;
NSArray *supportedTypeArray = session.supportedFileTypes;
if ([supportedTypeArray containsObject:AVFileTypeMPEG4]) {
session.outputFileType = AVFileTypeMPEG4;
} else if (supportedTypeArray.count == 0) {
if (failure) {
failure(@"该视频类型暂不支持导出", nil);
}
NSLog(@"No supported file types 视频类型暂不支持导出");
return;
} else {
session.outputFileType = [supportedTypeArray objectAtIndex:0];
}
if (![[NSFileManager defaultManager] fileExistsAtPath:[NSHomeDirectory() stringByAppendingFormat:@"/tmp"]]) {
[[NSFileManager defaultManager] createDirectoryAtPath:[NSHomeDirectory() stringByAppendingFormat:@"/tmp"] withIntermediateDirectories:YES attributes:nil error:nil];
}
//旋转角度相关
AVMutableVideoComposition *videoComposition = [self fixedCompositionWithAsset:composition degrees:[self degressFromVideoFileWithAsset:videoAsset]];
if (videoComposition.renderSize.width) {
// 修正视频转向
session.videoComposition = videoComposition;
}
// 合成完毕
[session exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
switch (session.status) {
case AVAssetExportSessionStatusUnknown: {
NSLog(@"AVAssetExportSessionStatusUnknown");
} break;
case AVAssetExportSessionStatusWaiting: {
NSLog(@"AVAssetExportSessionStatusWaiting");
} break;
case AVAssetExportSessionStatusExporting: {
NSLog(@"AVAssetExportSessionStatusExporting");
} break;
case AVAssetExportSessionStatusCompleted: {
NSLog(@"AVAssetExportSessionStatusCompleted");
if (success) {
success(outputPath);
}
} break;
case AVAssetExportSessionStatusFailed: {
NSLog(@"AVAssetExportSessionStatusFailed");
if (failure) {
failure(@"视频导出失败", session.error);
}
} break;
case AVAssetExportSessionStatusCancelled: {
NSLog(@"AVAssetExportSessionStatusCancelled");
if (failure) {
failure(@"导出任务已被取消", nil);
}
} break;
default: break;
}
});
}];
} else {
if (failure) {
NSString *errorMessage = [NSString stringWithFormat:@"当前设备不支持该预设:%@", presetName];
failure(errorMessage, nil);
}
}
}
#pragma mark - 获取优化后的视频转向信息
- (AVMutableVideoComposition *)fixedCompositionWithAsset:(AVAsset *)videoAsset degrees:(int)degrees {
AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
if (degrees != 0) {
CGAffineTransform translateToCenter;
CGAffineTransform mixedTransform;
videoComposition.frameDuration = CMTimeMake(1, 30);
NSArray *tracks = [videoAsset tracksWithMediaType:AVMediaTypeVideo];
AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
AVMutableVideoCompositionInstruction *roateInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
roateInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, [videoAsset duration]);
AVMutableVideoCompositionLayerInstruction *roateLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
if (degrees == 90) {
// 顺时针旋转90°
translateToCenter = CGAffineTransformMakeTranslation(videoTrack.naturalSize.height, 0.0);
mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI_2);
videoComposition.renderSize = CGSizeMake(videoTrack.naturalSize.height,videoTrack.naturalSize.width);
[roateLayerInstruction setTransform:mixedTransform atTime:kCMTimeZero];
} else if(degrees == 180){
// 顺时针旋转180°
translateToCenter = CGAffineTransformMakeTranslation(videoTrack.naturalSize.width, videoTrack.naturalSize.height);
mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI);
videoComposition.renderSize = CGSizeMake(videoTrack.naturalSize.width,videoTrack.naturalSize.height);
[roateLayerInstruction setTransform:mixedTransform atTime:kCMTimeZero];
} else if(degrees == 270){
// 顺时针旋转270°
translateToCenter = CGAffineTransformMakeTranslation(0.0, videoTrack.naturalSize.width);
mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI_2*3.0);
videoComposition.renderSize = CGSizeMake(videoTrack.naturalSize.height,videoTrack.naturalSize.width);
[roateLayerInstruction setTransform:mixedTransform atTime:kCMTimeZero];
}
roateInstruction.layerInstructions = @[roateLayerInstruction];
// 加入视频方向信息
videoComposition.instructions = @[roateInstruction];
}
return videoComposition;
}
#pragma mark - 导出函数
-(BOOL)exportMove
{
//加载媒体
NSURL* srcVideo = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"789.MP4" ofType:nil]];
AVURLAsset* videoAsset = [[AVURLAsset alloc] initWithURL:srcVideo options:nil];
// 查找当前媒体支持的presetName
NSArray *presets = [AVAssetExportSession exportPresetsCompatibleWithAsset:videoAsset];
NSLog(@"%@",presets);
if ([presets containsObject:AVAssetExportPreset640x480])
{
//导出视频文件到沙盒目录
[self startExportMP4VideoWithVideoAsset:videoAsset presetName:AVAssetExportPreset640x480 success:^(NSString *outputPath) {
NSLog(@" startExportMP4VideoWithVideoAsset success,path:%@",outputPath);
} failure:^(NSString *errorMessage, NSError *error) {
NSLog(@"startExportMP4VideoWithVideoAsset fail");
}];
}
else
{
NSLog(@"exportMove fail");
}
return YES;
}
//-----------------------------
//调用
//-----------------------------
[self exportMove];
三.猜你喜欢
- AVAsset 加载媒体
- AVAssetTrack 获取视频 音频信息
- AVMetadataItem 获取媒体属性元数据
- AVAssetImageGenerator 截图
- AVAssetImageGenerator 获取多帧图片
- AVAssetExportSession 裁剪/转码
- AVPlayer 播放视频
- AVPlayerItem 管理资源对象
- AVPlayerLayer 显示视频
- AVQueuePlayer 播放多个媒体文件
- AVComposition AVMutableComposition 将多个媒体合并
- AVAssetExportSession 导出视频到沙盒目录
ChatGPT 3.5 国内中文镜像站免费使用啦
暂无评论内容