使用AVFoundation合并视频片段输出黑屏无音频问题求助
AVFoundation多视频合并黑屏无音频问题修复方案
核心问题定位
- 视频画面不可见的根本原因:所有版本代码的图层指令都调用了
SetOpacity(0, endTime),将视频轨道的透明度设置为0,渲染时只能看到AVMutableVideoCompositionInstruction设置的背景色 - 音频缺失的根本原因:音频轨道添加逻辑用设备麦克风是否存在作为判断依据,和原视频是否包含音频轨完全无关,要么漏加音频要么在原视频无音频时触发崩溃
- 部分场景画面不可见的次要原因:仅对竖屏视频设置了变换规则,横屏视频无任何变换配置,可能导致画面超出渲染区域
- 初始版本崩溃原因:直接对未添加元素的空List执行
mainInstruction[0]赋值操作,触发数组越界
修复步骤
- 修正不透明度配置:将视频图层的不透明度设置为1,保证画面可见
- 修正音频添加逻辑:优先判断原视频资源是否包含音频轨,再执行轨道插入操作
- 补全视频变换规则:横竖屏视频都要配置正确的变换,保证画面适配渲染尺寸
- 修正指令初始化逻辑:正确实例化合成指令对象后再赋值属性
修复后完整代码
public void mergeclips() { AVMutableComposition mixComposition = new AVMutableComposition(); AVMutableVideoCompositionLayerInstruction[] Instruction_Array = new AVMutableVideoCompositionLayerInstruction[Clips.Count]; CMTime currentInsertTime = CMTime.Zero; foreach (string clip in Clips) { AVAsset asset = AVAsset.FromUrl(NSUrl.FromFilename(clip)); CMTimeRange range = new CMTimeRange() { Start = CMTime.Zero, Duration = asset.Duration }; // 添加视频轨道 AVMutableCompositionTrack videoTrack = mixComposition.AddMutableTrack(AVMediaType.Video, 0); AVAssetTrack assetVideoTrack = asset.TracksWithMediaType(AVMediaType.Video).FirstOrDefault(); if (assetVideoTrack != null) { videoTrack.InsertTimeRange(range, assetVideoTrack, currentInsertTime, out NSError videoError); if (videoError != null) { Console.WriteLine($"视频轨道插入失败:{videoError.LocalizedDescription}"); } } // 添加音频轨道 AVAssetTrack assetAudioTrack = asset.TracksWithMediaType(AVMediaType.Audio).FirstOrDefault(); if (assetAudioTrack != null) { AVMutableCompositionTrack audioTrack = mixComposition.AddMutableTrack(AVMediaType.Audio, 0); audioTrack.InsertTimeRange(range, assetAudioTrack, currentInsertTime, out NSError audioError); if (audioError != null) { Console.WriteLine($"音频轨道插入失败:{audioError.LocalizedDescription}"); } } // 生成图层指令 Instruction_Array[Clips.IndexOf(clip)] = SetInstruction(asset, currentInsertTime, videoTrack); // 更新插入时间点 currentInsertTime = CMTime.Add(currentInsertTime, asset.Duration); } // 配置合成指令 AVMutableVideoCompositionInstruction mainInstruction = new AVMutableVideoCompositionInstruction(); mainInstruction.TimeRange = new CMTimeRange() { Start = CMTime.Zero, Duration = mixComposition.Duration }; mainInstruction.BackgroundColor = UIColor.White.CGColor; mainInstruction.LayerInstructions = Instruction_Array; // 配置视频合成器 AVMutableVideoComposition mainComposition = new AVMutableVideoComposition() { Instructions = new AVVideoCompositionInstruction[] { mainInstruction }, FrameDuration = new CMTime(1, 30), RenderSize = new CGSize(UIScreen.MainScreen.Bounds.Width, UIScreen.MainScreen.Bounds.Height) }; // 导出配置 string outputPath = Path.Combine(Path.GetTempPath(), "temporaryClip/Whole.mov"); if (File.Exists(outputPath)) { File.Delete(outputPath); } AVAssetExportSession exportSession = new AVAssetExportSession(mixComposition, AVAssetExportSessionPreset.MediumQuality) { OutputUrl = NSUrl.FromFilename(outputPath), OutputFileType = AVFileType.QuickTimeMovie, ShouldOptimizeForNetworkUse = true, VideoComposition = mainComposition }; exportSession.ExportAsynchronously(_OnExportDone); } private AVMutableVideoCompositionLayerInstruction SetInstruction(AVAsset asset, CMTime currentTime, AVMutableCompositionTrack mixComposition_video_Track) { var instruction = AVMutableVideoCompositionLayerInstruction.FromAssetTrack(mixComposition_video_Track); AVAssetTrack assetTrack = asset.TracksWithMediaType(AVMediaType.Video)[0]; CGAffineTransform transform = assetTrack.PreferredTransform; CGSize transformSize = transform.TransformSize(assetTrack.NaturalSize); CGSize newAssetSize = new CGSize(Math.Abs(transformSize.Width), Math.Abs(transformSize.Height)); CGSize renderSize = new CGSize(UIScreen.MainScreen.Bounds.Width, UIScreen.MainScreen.Bounds.Height); // 统一计算缩放比例,适配渲染尺寸 nfloat scaleRatio = Math.Min(renderSize.Width / newAssetSize.Width, renderSize.Height / newAssetSize.Height); transform.Scale(scaleRatio, scaleRatio); // 计算偏移量,居中显示 nfloat tx = (renderSize.Width - newAssetSize.Width * scaleRatio) / 2; nfloat ty = (renderSize.Height - newAssetSize.Height * scaleRatio) / 2; transform.Translate(tx, ty); // 设置变换和不透明度 instruction.SetTransform(transform, currentTime); // 修复不透明度设置为1,保证画面可见 instruction.SetOpacity(1, currentTime); return instruction; } private static void _OnExportDone() { string outputPath = Path.Combine(Path.GetTempPath(), "temporaryClip/Whole.mov"); var library = new ALAssetsLibrary(); library.WriteVideoToSavedPhotosAlbum(NSUrl.FromFilename(outputPath), (path, e2) => { if (e2 != null) { new UIAlertView("Error", e2.ToString(), null, "OK", null).Show(); } }); }
额外注意事项
- 导出前需要先删除已存在的输出文件,否则AVAssetExportSession会直接导出失败
- 插入轨道时需要捕获错误信息,方便定位资源读取或插入失败的问题
- 渲染尺寸建议不要直接用屏幕尺寸,可固定为1080P等标准分辨率,避免不同设备导出的视频尺寸不一致
内容的提问来源于stack exchange,提问作者Jaime Santos
相关产品推荐
相关产品推荐

