199 lines
8.4 KiB
Objective-C
199 lines
8.4 KiB
Objective-C
//
|
||
// VChangeManager.m
|
||
// ProductApp
|
||
//
|
||
// Created by 工作 on 2025/4/8.
|
||
//
|
||
|
||
#import "VChangeManager.h"
|
||
#import <AVFoundation/AVFoundation.h>
|
||
|
||
@interface VChangeManager ()
|
||
{
|
||
AVAssetReader *assetReader;
|
||
AVAssetReaderTrackOutput *readerVideoTrackOutput, *readerAudioTrackOutput;
|
||
AVAssetWriter *assetWriter;
|
||
AVAssetWriterInput *assetVideoWriterInput, *assetAudioWriterInput;
|
||
}
|
||
|
||
@end
|
||
@implementation VChangeManager
|
||
+(id)manager
|
||
{
|
||
static dispatch_once_t once;
|
||
static VChangeManager *manager;
|
||
dispatch_once(&once, ^ {
|
||
manager = [[VChangeManager alloc] init];
|
||
});
|
||
return manager;
|
||
}
|
||
-(void)kaiShiZHUrl:(NSURL *)fileUrl back:(VChangeManagerBack)backValue
|
||
{
|
||
NSString *writerPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/12345.mp4"];
|
||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||
if ([fileManager fileExistsAtPath:writerPath]) {
|
||
[fileManager removeItemAtPath:writerPath error:nil];
|
||
}
|
||
|
||
AVAsset *asset = [AVAsset assetWithURL:fileUrl];
|
||
NSError *error;
|
||
//创建AVAssetReader对象用来读取asset数据
|
||
assetReader = [AVAssetReader assetReaderWithAsset:asset error:&error];
|
||
AVAsset *localAsset = assetReader.asset;
|
||
|
||
if([localAsset tracksWithMediaType:AVMediaTypeVideo].count==0 || [localAsset tracksWithMediaType:AVMediaTypeAudio].count==0)
|
||
{
|
||
backValue(NO,@"");
|
||
return;
|
||
}
|
||
AVAssetTrack *videoTrack = [[localAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
|
||
AVAssetTrack *audioTrack = [[localAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
|
||
|
||
NSDictionary *videoSetting = @{(id)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA],
|
||
(id)kCVPixelBufferIOSurfacePropertiesKey : [NSDictionary dictionary],
|
||
};
|
||
// AVAssetReaderTrackOutput用来设置怎么读数据
|
||
readerVideoTrackOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:videoTrack outputSettings:videoSetting];
|
||
//音频以pcm流的形似读数据
|
||
NSDictionary *audioSetting = @{AVFormatIDKey : [NSNumber numberWithUnsignedInt:kAudioFormatLinearPCM]};
|
||
readerAudioTrackOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:audioTrack outputSettings:audioSetting];
|
||
|
||
if ([assetReader canAddOutput:readerVideoTrackOutput]) {
|
||
[assetReader addOutput:readerVideoTrackOutput];
|
||
}
|
||
|
||
if ([assetReader canAddOutput:readerAudioTrackOutput]) {
|
||
[assetReader addOutput:readerAudioTrackOutput];
|
||
}
|
||
//开始读
|
||
[assetReader startReading];
|
||
|
||
NSURL *writerUrl = [NSURL fileURLWithPath:writerPath];
|
||
//创建一个写数据对象
|
||
assetWriter = [[AVAssetWriter alloc] initWithURL:writerUrl fileType:AVFileTypeMPEG4 error:nil];
|
||
//配置写数据,设置比特率,帧率等
|
||
NSDictionary *compressionProperties = @{ AVVideoAverageBitRateKey : @(1.38*1024*1024),
|
||
AVVideoExpectedSourceFrameRateKey: @(30),
|
||
AVVideoProfileLevelKey : AVVideoProfileLevelH264HighAutoLevel };
|
||
|
||
|
||
// 获取视频尺寸
|
||
CGSize videoSize = videoTrack.naturalSize;
|
||
int videoWidth = (int)videoSize.width;
|
||
int videoHeight = (int)videoSize.height;
|
||
//配置编码器宽高等
|
||
NSDictionary *compressionVideoSetting = @{
|
||
AVVideoCodecKey : AVVideoCodecTypeH264,
|
||
AVVideoWidthKey : @(videoWidth),
|
||
AVVideoHeightKey : @(videoHeight),
|
||
AVVideoCompressionPropertiesKey : compressionProperties
|
||
};
|
||
|
||
AudioChannelLayout stereoChannelLayout = {
|
||
.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo,
|
||
.mChannelBitmap = 0,
|
||
.mNumberChannelDescriptions = 0
|
||
};
|
||
NSData *channelLayoutAsData = [NSData dataWithBytes:&stereoChannelLayout length:offsetof(AudioChannelLayout, mChannelDescriptions)];
|
||
//写入音频配置
|
||
NSDictionary *compressionAudioSetting = @{
|
||
AVFormatIDKey : [NSNumber numberWithUnsignedInt:kAudioFormatMPEG4AAC],
|
||
AVEncoderBitRateKey : [NSNumber numberWithInteger:64000],
|
||
AVSampleRateKey : [NSNumber numberWithInteger:44100],
|
||
AVChannelLayoutKey : channelLayoutAsData,
|
||
AVNumberOfChannelsKey : [NSNumber numberWithUnsignedInteger:2]
|
||
};
|
||
//AVAssetWriterInput用来说明怎么写数据
|
||
assetVideoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:compressionVideoSetting];
|
||
assetAudioWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:compressionAudioSetting];
|
||
|
||
if ([assetWriter canAddInput:assetVideoWriterInput]) {
|
||
[assetWriter addInput:assetVideoWriterInput];
|
||
}
|
||
if ([assetWriter canAddInput:assetAudioWriterInput]) {
|
||
[assetWriter addInput:assetAudioWriterInput];
|
||
}
|
||
//开始写
|
||
[assetWriter startWriting];
|
||
[assetWriter startSessionAtSourceTime:kCMTimeZero];
|
||
dispatch_group_t group = dispatch_group_create();
|
||
dispatch_queue_t videoWriter = dispatch_queue_create("videoWriter", DISPATCH_QUEUE_CONCURRENT);
|
||
dispatch_queue_t audioWriter = dispatch_queue_create("audioWriter", DISPATCH_QUEUE_CONCURRENT);
|
||
__block BOOL isVideoComplete = NO;
|
||
dispatch_group_enter(group);
|
||
//要想写数据,就带有数据源,以下是将readerVideoTrackOutput读出来的数据加入到assetVideoWriterInput中再写入本地,音频和视频读取写入方式一样
|
||
__block BOOL issaveVideo = NO;
|
||
[assetVideoWriterInput requestMediaDataWhenReadyOnQueue:videoWriter usingBlock:^{
|
||
while (!isVideoComplete && assetVideoWriterInput.isReadyForMoreMediaData) {
|
||
//样本数据
|
||
@autoreleasepool {
|
||
//每次读取一个buffer
|
||
if(issaveVideo==NO)
|
||
{
|
||
issaveVideo = YES;
|
||
CMSampleBufferRef buffer = [readerVideoTrackOutput copyNextSampleBuffer];
|
||
if (buffer) {
|
||
//将读来的buffer加入到写对象中开始写
|
||
[assetVideoWriterInput appendSampleBuffer:buffer];
|
||
|
||
CFRelease(buffer);
|
||
buffer = NULL;
|
||
} else {
|
||
isVideoComplete = YES;
|
||
}
|
||
issaveVideo = NO;
|
||
}
|
||
}
|
||
|
||
}
|
||
if (isVideoComplete) {
|
||
//关闭写入会话
|
||
[assetVideoWriterInput markAsFinished];
|
||
dispatch_group_leave(group);
|
||
}
|
||
}];
|
||
__block BOOL isAudioComplete = NO;
|
||
__block BOOL issaveAudio = NO;
|
||
dispatch_group_enter(group);
|
||
[assetAudioWriterInput requestMediaDataWhenReadyOnQueue:audioWriter usingBlock:^{
|
||
while (!isAudioComplete && assetAudioWriterInput.isReadyForMoreMediaData) {
|
||
if(issaveAudio==NO)
|
||
{
|
||
issaveAudio = YES;
|
||
//样本数据
|
||
CMSampleBufferRef buffer = [readerAudioTrackOutput copyNextSampleBuffer];
|
||
if (buffer) {
|
||
[assetAudioWriterInput appendSampleBuffer:buffer];
|
||
CFRelease(buffer);
|
||
buffer = NULL;
|
||
} else {
|
||
isAudioComplete = YES;
|
||
}
|
||
issaveAudio = NO;
|
||
}
|
||
}
|
||
if (isAudioComplete) {
|
||
//关闭写入会话
|
||
[assetAudioWriterInput markAsFinished];
|
||
dispatch_group_leave(group);
|
||
}
|
||
}];
|
||
|
||
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
||
[assetWriter finishWritingWithCompletionHandler:^{
|
||
AVAssetWriterStatus status = assetWriter.status;
|
||
if (status == AVAssetWriterStatusCompleted) {
|
||
[assetReader cancelReading];
|
||
[assetWriter cancelWriting];
|
||
///成功
|
||
backValue(YES,writerPath);
|
||
} else {
|
||
///失败
|
||
backValue(NO,@"");
|
||
}
|
||
|
||
}];
|
||
});
|
||
}
|
||
@end
|