音視頻合成與壓縮

本文學習視頻的壓縮成,以及原本視頻中的音頻和需要合成的音頻的不同比例混合。
  • 大體思路 先創建新路徑放臨時壓縮視頻->壓縮視頻->合成音視頻
    1 創建新路徑
    self.tmpVideoPath = [tempDir stringByAppendingPathComponent:@"myMovie.mp4"]; ```
2 看新路徑存不存在 若之前存在則刪除
`NSURL *url = [NSURL fileURLWithPath:self.tmpVideoPath];`
`NSFileManager *fm = [NSFileManager defaultManager];`
`BOOL exist = [fm fileExistsAtPath:url.path];`
`if (exist) {`
        [fm removeItemAtURL:url error:&err];
        NSLog(@"file deleted");
        if (err) {
            NSLog(@"file remove error, %@", err.localizedDescription );
        }
  `  } else {`
        NSLog(@"no file by that name");
   ` }  `
3 壓縮視頻
> 壓縮前先了解 AVAsset是一個抽象類和不可變類,定義了媒體資源混合呈現的方式,通過asetWithURL方法進行創建時,實際上是創建了它子類AVUrlAsset的一個實例,而AVAsset是一個抽象類,不能直接被實例化。
通過AVUrlAsset我們可以創建一個帶選項(可選)的資產,以提供更精確的時長和計時信息
可以使用 exportPresetsCompatibleWithAsset: 方法檢查是否可以使用某個Preset.

``` NSURL *videoUrl = [NSURL fileURLWithPath:videoUrlString];
    self.asset = [AVAsset assetWithURL:videoUrl];
    AVAsset *anAsset = [[AVURLAsset alloc] initWithURL:videoUrl options:nil];
    NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:anAsset];
// 所支持的壓縮格式中是否有 所選的壓縮格式
    if ([compatiblePresets containsObject:AVAssetExportPresetMediumQuality]) {```
        
        self.exportSession = [[AVAssetExportSession alloc]initWithAsset:self.asset presetName:AVAssetExportPresetPassthrough];
        NSURL *furl = [NSURL fileURLWithPath:self.tmpVideoPath];
        self.exportSession.outputURL = furl;
       //文件用.mp4擴展名標識。
        self.exportSession.outputFileType = AVFileTypeMPEG4;
         //設置剪裁時間
        CMTime start = CMTimeMakeWithSeconds(self.startTime, self.asset.duration.timescale);
        CMTime duration = CMTimeMakeWithSeconds(self.stopTime - self.startTime, self.asset.duration.timescale);
        CMTimeRange range = CMTimeRangeMake(start, duration);
        self.exportSession.timeRange = range;
        
        [self.exportSession exportAsynchronouslyWithCompletionHandler:^{
            
            switch ([self.exportSession status]) {
                case AVAssetExportSessionStatusFailed:
                    
                    NSLog(@"Export failed: %@", [[self.exportSession error] localizedDescription]);
                    break;
                case AVAssetExportSessionStatusCancelled:
                    
                    break;
                default:
                  //壓縮成功
                    dispatch_async(dispatch_get_main_queue(), ^{
                        //混合音頻百分比
                        CGFloat progress = _smarkSlider.value/100;
                        //1-progress 是原視頻的所占百分比 progress是要插入音頻的所占百分比
                        [self addVideoVolumn:1-progress andAudioVolumn:progress];
                        //                        //創建一個消息對象
                        //                        NSNotification * notice = [NSNotification notificationWithName:@"PUSH" object:nil userInfo:nil];
                        //                        //發送消息
                        //                        [[NSNotificationCenter defaultCenter]postNotification:notice];
                        
                    });
                    
                    break;
            }
        }];  }

4 原視頻音頻和要插入音頻百分比混合成新的音頻
```/*
 
 *  抽取原視頻的音頻與需要的音樂混合
 
 */
-(void)addVideoVolumn:(CGFloat)volumnVideo andAudioVolumn:(CGFloat)volumnAudio
{
    
    AVMutableComposition *composition =[AVMutableComposition composition];
  //NSMutableArray *audioMixParams; 混合音數組
    _audioMixParams =[[NSMutableArray alloc]initWithCapacity:0];
    // 錄制的視頻(剛才壓縮的視頻)
    NSURL *video_inputFileUrl =[[NSURL alloc]  initFileURLWithPath:self.tmpVideoPath];
    AVURLAsset *songAsset =[AVURLAsset URLAssetWithURL:video_inputFileUrl options:nil];
    CMTime startTime = CMTimeMakeWithSeconds(0, songAsset.duration.timescale);
    CMTime trackDuration = songAsset.duration;
    
    // 獲取視頻中的音頻素材
    [self setUpAndAddAudioAtPath:video_inputFileUrl toComposition:composition start:startTime dura:trackDuration offset:CMTimeMake(0,44100) andVolume:volumnVideo];
    
    // 本地要插入的音樂
    NSURL *url = [[NSURL alloc]initFileURLWithPath:[[AudioDao getCurrentWorkDataByWorkdate:_model.audioTime].newfile getFilePathOfDocuments]];//根據時間獲取音頻的路徑 ##**********
    
    // 獲取設置完的藍夾子音素材
    AVURLAsset *songAsset1 =[AVURLAsset URLAssetWithURL:url options:nil];
    CMTime startTime1 = CMTimeMakeWithSeconds(0.06 + self.startTime, songAsset1.duration.timescale);// 設置。 1.1
    CMTime trackDuration1 = songAsset1.duration;
    
    [self setUpAndAddAudioAtPath:url toComposition:composition start:startTime1 dura:trackDuration1 offset:CMTimeMake(0,44100) andVolume:volumnAudio];
    
    // 創建一個可變的音頻混合
    AVMutableAudioMix *audioMix =[AVMutableAudioMix audioMix];
    audioMix.inputParameters =[NSArray arrayWithArray:_audioMixParams];//從數組里取出處理后的音頻軌道參數
    
    // 創建一個輸出
    AVAssetExportSession *exporter =[[AVAssetExportSession alloc]
                                     initWithAsset:composition
                                     presetName:AVAssetExportPresetAppleM4A];
    exporter.audioMix = audioMix;
    exporter.outputFileType= AVFileTypeAppleM4A;// @"com.apple.m4a-audio"
    NSString* fileName =[NSString stringWithFormat:@"%@.mov",@"overMix"];// 不能改格式
    // 輸出路徑
    NSString *exportFile =[NSString stringWithFormat:@"%@/%@",[self getLibarayPath], fileName];
    
    if([[NSFileManager defaultManager]fileExistsAtPath:exportFile]) {
        [[NSFileManager defaultManager]removeItemAtPath:exportFile error:nil];
    }
    
    
    NSURL *exportURL =[NSURL fileURLWithPath:exportFile];
    exporter.outputURL = exportURL;
    self.mixURL = exportURL;
    
    __weak typeof(self) weakSelf = self;
    [exporter exportAsynchronouslyWithCompletionHandler:^{
        int exportStatus =(int)exporter.status;
        switch (exportStatus){
            case AVAssetExportSessionStatusFailed:{
                NSError *exportError = exporter.error;
                NSLog(@"錯誤,信息: %@", exportError);
                
                break;
            }
            case AVAssetExportSessionStatusCompleted:{
                NSLog(@"成功 是否在主線程2%d",(int)[NSThread isMainThread]);
                // 最終混合
                [weakSelf theVideoWithMixMusic];
                
                break;
            }
            case AVAssetExportSessionStatusExporting:{
                 NSLog(@"當前壓縮進度:%f",exporter.progress);

    
                
                break;
            }
        }
    }];
}```

```/*
 
 * 通過文件路徑建立和添加音頻素材
 
 */
- (void)setUpAndAddAudioAtPath:(NSURL*)assetURL toComposition:(AVMutableComposition*)composition start:(CMTime)start dura:(CMTime)dura offset:(CMTime)offset andVolume:(float)volumn{
    
    AVURLAsset *songAsset =[AVURLAsset URLAssetWithURL:assetURL options:nil];
    
    AVMutableCompositionTrack *track =[composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    AVAssetTrack *sourceAudioTrack =[[songAsset tracksWithMediaType:AVMediaTypeAudio]objectAtIndex:0];
    NSError *error =nil;
    BOOL ok = NO;
    CMTime startTime = start;
    CMTime trackDuration = dura;
    CMTimeRange tRange = CMTimeRangeMake(startTime,trackDuration);
    
    // 設置音量 設置從指定時間開始的音頻音量的值。
    //AVMutableAudioMixInputParameters(輸入參數可變的音頻混合)
    //audioMixInputParametersWithTrack(音頻混音輸入參數與軌道)
    AVMutableAudioMixInputParameters *trackMix =[AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:track];
    [trackMix setVolume:volumn atTime:startTime];
    
    // 素材加入數組
    [_audioMixParams addObject:trackMix];
    
    // Insert audio into track  //offsetCMTimeMake(0, 44100)
    ok = [track insertTimeRange:tRange ofTrack:sourceAudioTrack atTime:offset error:&error];
    
}```
5 把視頻和混合的音頻合成

-(void)theVideoWithMixMusic
{
// 聲音來源路徑(最終混合的音頻)
NSURL *audio_inputFileUrl = self.mixURL;

// 視頻來源路徑
NSURL   *video_inputFileUrl = [NSURL fileURLWithPath:self.tmpVideoPath];

// 最終合成輸出路徑
_outputFilePath =[documentsDirectory stringByAppendingPathComponent:@"finalvideo.mp4"];
NSURL
*outputFileUrl = [NSURL fileURLWithPath:_outputFilePath];

if([[NSFileManager defaultManager]fileExistsAtPath:_outputFilePath])
    [[NSFileManager defaultManager]removeItemAtPath:_outputFilePath error:nil];

CMTime nextClipStartTime =kCMTimeZero;

// 創建可變的音頻視頻組合
AVMutableComposition* mixComposition =[AVMutableComposition composition];

// 視頻采集

AVURLAsset* videoAsset =[[AVURLAsset alloc]initWithURL:video_inputFileUrl options:nil];
CMTimeRange video_timeRange = CMTimeRangeMake(kCMTimeZero,videoAsset.duration);
AVMutableCompositionTrack *a_compositionVideoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];

[a_compositionVideoTrack insertTimeRange:video_timeRange ofTrack:[[videoAsset tracksWithMediaType:AVMediaTypeVideo]objectAtIndex:0] atTime:nextClipStartTime error:nil];

// 聲音采集
AVURLAsset* audioAsset =[[AVURLAsset alloc]initWithURL:audio_inputFileUrl options:nil];


CMTimeRange audio_timeRange =CMTimeRangeMake(kCMTimeZero,videoAsset.duration);//聲音長度截取范圍
AVMutableCompositionTrack *b_compositionAudioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
[b_compositionAudioTrack insertTimeRange:audio_timeRange ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio]objectAtIndex:0]atTime:nextClipStartTime error:nil];

AVMutableVideoComposition *mainComposition = [AVMutableVideoComposition videoComposition];
mainComposition.frameDuration = CMTimeMake(1, 30);

NSInteger videoRoate = [self.class degressFromVideoFileWithURL:video_inputFileUrl];
CGAffineTransform translateToCenter;
//    if(videoRoate!=0){
CGAffineTransform mixedTransform;
if(videoRoate == 0){
    
    translateToCenter = CGAffineTransformMakeTranslation(0.0,0.0);
    mixedTransform = CGAffineTransformRotate(translateToCenter,0);
    mainComposition.renderSize = CGSizeMake(a_compositionVideoTrack.naturalSize.width,a_compositionVideoTrack.naturalSize.height);
}
else if(videoRoate == 90){
    //順時針旋轉90°
    NSLog(@"視頻旋轉90度,home按鍵在左");
    translateToCenter = CGAffineTransformMakeTranslation(a_compositionVideoTrack.naturalSize.height,0.0);
    mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI_2);
    mainComposition.renderSize = CGSizeMake(a_compositionVideoTrack.naturalSize.height,a_compositionVideoTrack.naturalSize.width);
}else if(videoRoate == 180){
    //順時針旋轉180°
    NSLog(@"視頻旋轉180度,home按鍵在上");
    translateToCenter = CGAffineTransformMakeTranslation(a_compositionVideoTrack.naturalSize.width, a_compositionVideoTrack.naturalSize.height);
    mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI);
    mainComposition.renderSize = CGSizeMake(a_compositionVideoTrack.naturalSize.width,a_compositionVideoTrack.naturalSize.height);
}else if(videoRoate == 270){
    //順時針旋轉270°
    NSLog(@"視頻旋轉270度,home按鍵在右");
    translateToCenter = CGAffineTransformMakeTranslation(0.0, a_compositionVideoTrack.naturalSize.width);
    mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI_2*3.0);
    mainComposition.renderSize = CGSizeMake(a_compositionVideoTrack.naturalSize.height,a_compositionVideoTrack.naturalSize.width);
}
AVMutableVideoCompositionInstruction *roateInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
roateInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, [mixComposition duration]);
AVMutableVideoCompositionLayerInstruction *roateLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:a_compositionVideoTrack];

[roateLayerInstruction setTransform:mixedTransform atTime:kCMTimeZero];
roateInstruction.layerInstructions = @[roateLayerInstruction];
//將視頻方向旋轉加入到視頻處理中
mainComposition.instructions = @[roateInstruction];
//    }
// 創建一個輸出
_assetExport =[[AVAssetExportSession alloc]initWithAsset:mixComposition presetName:_exportSet];
_assetExport.outputFileType =AVFileTypeMPEG4;
_assetExport.outputURL = outputFileUrl;
_assetExport.shouldOptimizeForNetworkUse=YES;
_assetExport.videoComposition = mainComposition;

__weak typeof(self) weakSelf = self;

// NSTimer *processTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1 target: self selector: @selector(exportingProgressDicChanged) userInfo: nil repeats: YES];
//
// [processTimer fire];

// 獲得隊列
//    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue =dispatch_get_main_queue();

// 創建一個定時器(dispatch_source_t本質還是個OC對象)
[self setProgressView];
_timer=dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, queue);
dispatch_time_t start =dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
uint64_t interval = (uint64_t)(0.1 *NSEC_PER_SEC);
dispatch_source_set_timer(_timer, start, interval,0);
// 設置回調
dispatch_source_set_event_handler(_timer, ^{
    [self exportingProgressDicChanged];
});
dispatch_resume(_timer);

// dispatch_async(queue, ^{
[_assetExport exportAsynchronouslyWithCompletionHandler:
^(void ) {
// if ([_assetExport status] == AVAssetExportSessionStatusCompleted) {
dispatch_async(dispatch_get_main_queue(), ^{
[LCProgressHUD showMessage:@"合成完成"];
});
}
];
// });
NSLog(@"完成!輸出路徑==%@",_outputFilePath);
}```

其實有很多概念 屬性我都不是很確定是做什么的 所以這邊我單拿出來 解釋一下

  • CMTime start = CMTimeMakeWithSeconds(self.startTime, self.asset.duration.timescale);
    https://depthlove.github.io/2016/03/15/CMTime-CMTimeMake-CMTimeMakeWithSeconds-in-iOS-encode/ 寫的很詳細
    CMTimeMake(a,b) a當前第幾幀, b每秒鐘多少幀.當前播放時間a/b
    CMTimeMakeWithSeconds(a,b) a當前時間,b每秒鐘多少幀.
  • AVAsset和AVUrlAsset的區別
    AVURLAsset
    是用于AVAsset
    從本地或遠程URL初始化資產的具體子類。繼承自
    AVAsset
  • 碼率:通俗一點的理解就是取樣率,單位時間內取樣率越大,精度就越高,處理出來的文件就越接近原始文件,但是文件體積與取樣率是成正比的,所以幾乎所有的編碼格式重視的都是如何用最低的碼率達到最少的失真 【碼率】(kbps)=【文件大小】(MB) /【時間】(秒)
    碼率幾點原則:
    1、碼率和質量成正比,但是文件體積也和碼率成正比。這是要牢記的。
    2、碼率超過一定數值,對圖像的質量沒有多大影響。
    3、DVD的容量有限,無論是標準的4.3G,還是超刻,或是D9,都有極限。視頻碼率 計算機中的信息都是二進制的0和1來表示,其中每一個0或1被稱作一個位,用小寫b表示,即bit(位);大寫B表示byte,即字節,一個字節=八個位,即1B=8b;前面的大寫K表示1024的意思,即1024個位(Kb)或1024個字節(KB)。表示文件的大小單位,一般都使用字節(KB)來表示文件的大小。
    Kbps:首先要了解的是,ps指的是/s,即每秒。Kbps指的是網絡速度,也就是每秒鐘傳送多少個千位的信息(K表示千位,Kb表示的是多少千個位),為了在直觀上顯得網絡的傳輸速度較快,一般公司都使用kb(千位)來表示。1KB/S=8Kbps。ADSL上網時的網速是512Kbps,如果轉換成字節,就是512/8=64KB/S(即64千字節每秒)。
    4、一般來說,如果是1M的寬帶,在網上只能看不超過1024kbps的視頻,超過1024kbps的視頻只能等視頻緩沖才能順利觀看。
demo我還沒時間整理出來~但是一定會更新的 大家有問題就一起討論 寫的不對的地方請大家指出來
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容