You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用AVFoundation合并视频片段输出黑屏无音频问题求助

AVFoundation多视频合并黑屏无音频问题修复方案

核心问题定位

  • 视频画面不可见的根本原因:所有版本代码的图层指令都调用了SetOpacity(0, endTime),将视频轨道的透明度设置为0,渲染时只能看到AVMutableVideoCompositionInstruction设置的背景色
  • 音频缺失的根本原因:音频轨道添加逻辑用设备麦克风是否存在作为判断依据,和原视频是否包含音频轨完全无关,要么漏加音频要么在原视频无音频时触发崩溃
  • 部分场景画面不可见的次要原因:仅对竖屏视频设置了变换规则,横屏视频无任何变换配置,可能导致画面超出渲染区域
  • 初始版本崩溃原因:直接对未添加元素的空List执行mainInstruction[0]赋值操作,触发数组越界

修复步骤

  1. 修正不透明度配置:将视频图层的不透明度设置为1,保证画面可见
  2. 修正音频添加逻辑:优先判断原视频资源是否包含音频轨,再执行轨道插入操作
  3. 补全视频变换规则:横竖屏视频都要配置正确的变换,保证画面适配渲染尺寸
  4. 修正指令初始化逻辑:正确实例化合成指令对象后再赋值属性

修复后完整代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 09:06:03