iOS——給視頻添加音頻和字幕

最近需要做一個(gè)功能嘹朗,給視頻添加字幕师妙,關(guān)鍵還是OC的代碼,愁啊屹培。
這種沒做過的東西就只能面對(duì)搜索引擎編程了默穴。
各種搜索和嘗試,最后弄了兩天給弄出來了褪秀,唯一不足的就是字幕的清晰度不夠蓄诽,也暫時(shí)不知道是不是視頻合成質(zhì)量的問題。

簡(jiǎn)單說一下整個(gè)流程的思路及關(guān)鍵點(diǎn):
1. 創(chuàng)建AVMutableComposition 和 AVMutableCompositionTrack
AVMutableComposition *mix = [AVMutableComposition composition];
// 視頻軌道
AVMutableCompositionTrack *videoTrack = [mix addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
//音頻軌道
AVMutableCompositionTrack *audioTrack = [mix addMutableTrackWithMediaType:AVMediaTypeAudio
            preferredTrackID:kCMPersistentTrackID_Invalid];
2. 把資源放進(jìn)Track
[videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] atTime:videoTrack.asset.duration error:nil];
[AudioTrack insertTimeRange:CMTimeRangeFromTimeToTime(kCMTimeZero, videoTrack.asset.duration) ofTrack:[[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:kCMTimeZero error:nil];
3.創(chuàng)建導(dǎo)出
self.exporter = [[AVAssetExportSession alloc] initWithAsset:mix presetName:AVAssetExportPreset3840x2160];
4. AVMutableVideoComposition 和 AVMutableVideoCompositionInstruction
//AVMutableVideoComposition:管理所有視頻軌道溜歪,可以決定最終視頻的尺寸若专,裁剪需要在這里進(jìn)行
AVMutableVideoComposition *mainCompositionInst = [AVMutableVideoComposition videoComposition];
// AVMutableVideoCompositionInstruction 視頻軌道中的一個(gè)視頻,可以縮放蝴猪、旋轉(zhuǎn)等
AVMutableVideoCompositionInstruction *mainInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
5.添加字幕或水印
// videoLayer是視頻layer,parentLayer是最主要的调衰,videoLayer和字幕,貼紙的layer都要加在該layer上
CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];
...
[parentLayer addSublayer: 字幕layer];
因?yàn)橹饕褪且砑幼帜蛔在澹赃@里的邏輯我會(huì)重點(diǎn)說一下嚎莉。
字幕其實(shí)就是放在視頻layer層上的文字(label),但是它們是隱藏的沛豌,并且在指定時(shí)間出現(xiàn)又在指定時(shí)間消失趋箩。也就是說要給這個(gè)layer加上一個(gè)動(dòng)畫組,第一個(gè)動(dòng)畫是將透明度變?yōu)?加派,第二個(gè)動(dòng)畫將透明度變?yōu)?
將字幕文件解析后叫确,將動(dòng)畫組的beginTime設(shè)為開始時(shí)間,duration設(shè)為持續(xù)時(shí)間芍锦,同時(shí)將消失動(dòng)畫的beginTime設(shè)為持續(xù)時(shí)間竹勉,duration設(shè)為0.1
6. AVVideoCompositionCoreAnimationTool 以及 exporter的賦值
mainCompositionInst.animationTool = [AVVideoCompositionCoreAnimationTool
                                         videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
/// 設(shè)置字幕 layer層
    self.exporter.videoComposition = mainCompositionInst;
7.導(dǎo)出
[self.exporter exportAsynchronouslyWithCompletionHandler:^{
        dispatch_async(dispatch_get_main_queue(), ^{
        });
}];

感謝愛我你就抱抱我——ios 通過AVFoundation給視頻添加字幕的這篇文章,不然我對(duì)添加字幕毫無頭緒娄琉。雖然文章里面沒有完整的字幕動(dòng)畫代碼...

完整代碼如下次乓,這里面的代碼是有實(shí)際業(yè)務(wù)需求的代碼吓歇,所以會(huì)有一些變動(dòng)。
另外添加字幕label的時(shí)候我碰到了一個(gè)特別奇怪的問題票腰。如果只添加一個(gè)label.layer城看,沒有問題,如果放入for循環(huán)中循環(huán)添加杏慰,一個(gè)都加不上去测柠。嘗試半天不知道為什么,最后靈機(jī)一動(dòng)逃默,加了一句添加label到view上鹃愤,結(jié)果就可以了。
然后由于label添加的字幕清晰度不行完域,暫時(shí)也不知道是視頻合成的原因還是說自己分辨率造成的。所以換成了CATextLayer瘩将,比label清晰了那么一丟丟吟税。但是發(fā)現(xiàn)我設(shè)置了textLayer.contentsScale = [UIScreen mainScreen].scale;反而更不清晰了,所以就屏蔽了這個(gè)contentsScale
/**
 @param videos 需要被合成的音頻文件
 @param audios 需要被合成的視頻文件
 */
- (void)compositionVideos:(NSArray <NSURL *>*)videos audios:(NSArray <NSURL *>*)audios
{
    [SVProgressHUD setDefaultMaskType:(SVProgressHUDMaskTypeClear)];
    [SVProgressHUD showWithStatus:@"正在合成..."];
    
    NSMutableArray *videosAsset = [NSMutableArray arrayWithCapacity:0];
    NSMutableArray *audiosAsset = [NSMutableArray arrayWithCapacity:0];
    
    for (NSURL *videoPath in videos) {
        AVAsset *asset = [AVAsset assetWithURL:videoPath];
        NSLog(@"%@",asset);
        [videosAsset insertObject:asset atIndex:videosAsset.count];
    }
    
    for (NSURL *audioPath in audios) {
        AVAsset *asset = [AVAsset assetWithURL:audioPath];
        [audiosAsset insertObject:asset atIndex:audiosAsset.count];
    }
    
    // 1 - 創(chuàng)建 AVMutableComposition 對(duì)象. 對(duì)象將保存AVMutableCompositionTrack實(shí)例.
    AVMutableComposition *mix = [AVMutableComposition composition];
    // 2 - 視頻軌道
    AVMutableCompositionTrack *videoTrack = [mix addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    
    [videosAsset enumerateObjectsUsingBlock:^(AVAsset *asset, NSUInteger idx, BOOL *stop) {
        [videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] atTime:videoTrack.asset.duration error:nil];
    }];
    
    NSMutableArray *audioTrackes = [NSMutableArray arrayWithCapacity:0];
    [audiosAsset enumerateObjectsUsingBlock:^(AVAsset *asset, NSUInteger idx, BOOL *stop) {
        AVMutableCompositionTrack *AudioTrack = [mix addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:(int32_t)idx];
        [AudioTrack insertTimeRange:CMTimeRangeFromTimeToTime(kCMTimeZero, videoTrack.asset.duration) ofTrack:[[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:kCMTimeZero error:nil];
        [audioTrackes addObject:AudioTrack];
    }];
    
    // 4 - 獲取路徑
    NSURL *url = [EditAudioVideo exporterVideoPath];
    
    // 5 - 創(chuàng)建導(dǎo)出
    self.exporter = [[AVAssetExportSession alloc] initWithAsset:mix presetName:AVAssetExportPreset3840x2160];
    
    //修改背景音樂的音量start
    AVMutableAudioMix *videoAudioMixTools = [AVMutableAudioMix audioMix];
    
    //獲取音頻軌道
    NSMutableArray *inputParameters = [NSMutableArray arrayWithCapacity:0];
    [audiosAsset enumerateObjectsUsingBlock:^(AVAsset *asset, NSUInteger idx, BOOL *stop) {
        AVMutableAudioMixInputParameters *audioParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:audioTrackes[idx]];
        [audioParameters setVolumeRampFromStartVolume:1.0 toEndVolume:1.0 timeRange:CMTimeRangeMake(kCMTimeZero, CMTimeAdd(kCMTimeZero, asset.duration))];
        
        [audioParameters setTrackID:(int32_t)idx];
        
        [inputParameters addObject:audioParameters];
    }];
    
    videoAudioMixTools.inputParameters = inputParameters;
    
    //3.1 AVMutableVideoCompositionInstruction 視頻軌道中的一個(gè)視頻姿现,可以縮放肠仪、旋轉(zhuǎn)等
    AVMutableVideoCompositionInstruction *mainInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, videoTrack.asset.duration);
    
    /// 3.2 AVMutableVideoCompositionLayerInstruction 一個(gè)視頻軌道,包含了這個(gè)軌道上的所有視頻素材
    /// 必須依靠它
    AVMutableVideoCompositionLayerInstruction *videolayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
    //1 創(chuàng)建AVAsset實(shí)例 AVAsset包含了video的所有信息 self.videoUrl輸入視頻的路徑
    AVAssetTrack *videoAssetTrack = [[videoTrack.asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
    UIImageOrientation videoAssetOrientation_  = UIImageOrientationUp;
    BOOL isVideoAssetPortrait_  = NO;
    CGAffineTransform videoTransform = videoAssetTrack.preferredTransform;
    if (videoTransform.a == 0 && videoTransform.b == 1.0 && videoTransform.c == -1.0 && videoTransform.d == 0) {
        videoAssetOrientation_ = UIImageOrientationRight;
        isVideoAssetPortrait_ = YES;
    }
    if (videoTransform.a == 0 && videoTransform.b == -1.0 && videoTransform.c == 1.0 && videoTransform.d == 0) {
        videoAssetOrientation_ =  UIImageOrientationLeft;
        isVideoAssetPortrait_ = YES;
    }
    if (videoTransform.a == 1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == 1.0) {
        videoAssetOrientation_ =  UIImageOrientationUp;
    }
    if (videoTransform.a == -1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == -1.0) {
        videoAssetOrientation_ = UIImageOrientationDown;
    }
    [videolayerInstruction setTransform:videoAssetTrack.preferredTransform atTime:kCMTimeZero];
    [videolayerInstruction setOpacity:0.0 atTime:videoTrack.asset.duration];
    
    mainInstruction.layerInstructions = [NSArray arrayWithObjects:videolayerInstruction,nil];
    
    //AVMutableVideoComposition:管理所有視頻軌道备典,可以決定最終視頻的尺寸异旧,裁剪需要在這里進(jìn)行
    AVMutableVideoComposition *mainCompositionInst = [AVMutableVideoComposition videoComposition];
    
    CGSize naturalSize;
    if(isVideoAssetPortrait_){
        naturalSize = CGSizeMake(videoTrack.naturalSize.height, videoTrack.naturalSize.width);
    } else {
        naturalSize = videoTrack.naturalSize;
    }
    
    /// 設(shè)置高寬
    float renderWidth, renderHeight;
    renderWidth = naturalSize.width;
    renderHeight = naturalSize.height;
    mainCompositionInst.renderSize = CGSizeMake(renderWidth, renderHeight);
    mainCompositionInst.instructions = [NSArray arrayWithObject:mainInstruction];
    mainCompositionInst.frameDuration = CMTimeMake(1, 30);
    
    /// 加水印 加字幕
    // videoLayer是視頻layer,parentLayer是最主要的,videoLayer和字幕提佣,貼紙的layer都要加在該layer上
    CALayer *parentLayer = [CALayer layer];
    CALayer *videoLayer = [CALayer layer];
    parentLayer.frame = CGRectMake(0, 0, renderWidth, renderHeight);
    videoLayer.frame = CGRectMake(0, 0, renderWidth, renderHeight);
    [parentLayer addSublayer:videoLayer];
    
    for (SubTitleManager *obj in [SubTitleManager manager].subTitles) {
        
//        UILabel * titleLabel = [[UILabel alloc]initWithFrame: CGRectMake(8, 8, kScreenW - 16, 38)];
//        titleLabel.attributedText = [[SubTitleManager manager] getSRTSubtitleWithCurrentObj:obj];
//        titleLabel.minimumScaleFactor = 9/14;
//        titleLabel.adjustsFontSizeToFitWidth = true;
//        titleLabel.numberOfLines = 3;
//        titleLabel.shadowOffset = CGSizeMake(0, -1);
//        titleLabel.textAlignment = NSTextAlignmentCenter;
//        titleLabel.layer.opacity = 0;
        
        CATextLayer * textLayer = [CATextLayer layer];
        textLayer.string = [[SubTitleManager manager] getSRTSubtitleWithCurrentObj:obj];
        textLayer.frame = CGRectMake(8, 8, kScreenW - 16, 38);
        textLayer.wrapped = YES;
//        textLayer.contentsScale = [UIScreen mainScreen].scale;
        textLayer.alignmentMode = kCAAlignmentCenter;
        textLayer.truncationMode = kCATruncationNone;
        textLayer.opacity = 0;
        
        // 這個(gè)是透明度動(dòng)畫主要是使在插入的才顯示吮蛹,其它時(shí)候都是不顯示的
        CABasicAnimation *opacityAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];
        opacityAnim.fromValue = [NSNumber numberWithFloat:1];
        opacityAnim.toValue = [NSNumber numberWithFloat:1];
        opacityAnim.removedOnCompletion = NO;
        
        CABasicAnimation *opacityDismissAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];
        opacityDismissAnim.fromValue = [NSNumber numberWithFloat:1];
        opacityDismissAnim.toValue = [NSNumber numberWithFloat:0];
        opacityDismissAnim.removedOnCompletion = NO;
        opacityDismissAnim.beginTime = obj.endTime - obj.startTime;
        opacityDismissAnim.duration = 0.1;
        
        CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
        groupAnimation.animations = [NSArray arrayWithObjects:opacityAnim, opacityDismissAnim, nil];
        
        if (obj.startTime == 0) {
            groupAnimation.beginTime = 0.01;
        } else {
            groupAnimation.beginTime = obj.startTime;
        }
        groupAnimation.duration = obj.endTime - obj.startTime;
        [textLayer addAnimation:groupAnimation forKey:@"groupAnimation"];
        [parentLayer addSublayer: textLayer];
    }
    
    mainCompositionInst.animationTool = [AVVideoCompositionCoreAnimationTool
                                         videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
    self.exporter.outputURL = url;
    self.exporter.outputFileType = AVFileTypeQuickTimeMovie;
    /// 賦值音頻
    self.exporter.audioMix = videoAudioMixTools;
    self.exporter.shouldOptimizeForNetworkUse = YES;
    /// 設(shè)置字幕 layer層
    self.exporter.videoComposition = mainCompositionInst;
    
    __weak typeof(self) wSelf = self;
    [self.exporter exportAsynchronouslyWithCompletionHandler:^{
        dispatch_async(dispatch_get_main_queue(), ^{
            [SVProgressHUD showWithStatus:@"錄制合成完成"];
            [wSelf exportDidFinish:wSelf.exporter];
        });
    }];
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市拌屏,隨后出現(xiàn)的幾起案子潮针,更是在濱河造成了極大的恐慌,老刑警劉巖倚喂,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件每篷,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡端圈,警方通過查閱死者的電腦和手機(jī)焦读,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舱权,“玉大人矗晃,你說我怎么就攤上這事⌒糖桑” “怎么了喧兄?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵无畔,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我吠冤,道長(zhǎng)浑彰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任拯辙,我火速辦了婚禮郭变,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涯保。我一直安慰自己诉濒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布夕春。 她就那樣靜靜地躺著未荒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪及志。 梳的紋絲不亂的頭發(fā)上片排,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音速侈,去河邊找鬼率寡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛倚搬,可吹牛的內(nèi)容都是我干的冶共。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼每界,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼捅僵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起盆犁,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤命咐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后谐岁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體醋奠,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片春哨。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡熟呛,死狀恐怖沙峻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤级乐,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布议薪,位于F島的核電站尤蛮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏斯议。R本人自食惡果不足惜产捞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望哼御。 院中可真熱鬧坯临,春花似錦、人聲如沸恋昼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)液肌。三九已至挟炬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間矩屁,已是汗流浹背辟宗。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吝秕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓空幻,卻偏偏與公主長(zhǎng)得像烁峭,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秕铛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354