AKS部署.NET版Teams录制机器人视频处理、ZIP存储及S3上传方案
Teams录制机器人视频流处理、本地打包及S3上传实现方案
一、补全视频流接收逻辑
你现有代码已经完成了视频套接字的事件订阅,只需要补全事件回调和数据结构即可,不要在回调线程里做重IO操作,避免阻塞媒体线程导致丢帧:
- 首先定义和音频缓冲区对齐的可序列化视频数据结构:
public class SerializableVideoMediaBuffer { public string SocketId { get; set; } public long Timestamp { get; set; } public byte[] Buffer { get; set; } public string ParticipantId { get; set; } public bool IsVbss { get; set; } public int Width { get; set; } public int Height { get; set; } public float Fps { get; set; } } - 实现
OnVideoMediaReceived和OnVbssMediaReceived回调,把收到的H264帧封装成上述结构,投递到VideoProcessor的队列中。注意Teams下发的H264是Annex-B格式的NALU,首帧会携带SPS/PPS参数集,不需要手动拼接,直接按接收顺序投递即可。 - 参会人视频套接字是动态绑定参会人流的,收到某Socket第一帧数据时,关联当前开摄像头的参会人ID即可,不需要提前初始化所有套接字的写入器。
二、实现VideoProcessor(对齐现有AudioProcessor架构)
完全复用你现有AudioProcessor的生产者-消费者模式,继承BufferBase<SerializableVideoMediaBuffer>,不需要调整现有媒体流框架逻辑:
- 用
FFmpeg.AutoGen做H264裸流到MP4容器的封装,不要做重编码,直接拷贝H264帧到MP4容器,CPU占用可以降到最低。部署时把对应平台的FFmpeg二进制打进AKS镜像即可,不需要额外服务。 - 内部维护一个
Dictionary<string, IntPtr>格式的写入器字典,key分别对应参会人ID、固定值vbss,收到首帧时初始化对应的MP4写入上下文,设置正确的时间基、分辨率、帧率参数。 Process方法里按时间戳顺序把H264帧写入对应上下文,不要跨帧打乱顺序,否则会出现花屏、音画不同步问题。Finalise方法逻辑和音频完全对齐:先排空队列内剩余帧,然后写入MP4文件尾、释放所有FFmpeg上下文,输出所有生成的MP4文件路径。
三、统一ZIP打包逻辑
现有AudioProcessor的打包逻辑可以直接复用,不需要重写:
- 会议结束触发录制终止时,先等待AudioProcessor和VideoProcessor都执行完Finalise,拿到所有音频WAV、视频MP4的本地路径。
- 创建ZIP归档时按目录分类存放:
audio/目录存放所有参会人单独音频、混音音频文件video/participants/目录存放每个参会人的摄像头视频文件video/screen_share/目录存放VBSS屏幕共享录制文件
- 所有文件写入ZIP时用最优压缩级别,视频文件本身已经是压缩编码,压缩速度很快。写入完成后删除临时生成的原始WAV、MP4文件,只保留最终ZIP包。
四、S3存储上传实现
直接用官方AWSSDK.S3实现,兼容所有支持S3协议的对象存储服务:
- 扩展现有配置类,增加S3连接参数:访问密钥、私密密钥、服务地址、存储桶名称、是否启用路径样式访问。
- ZIP包生成完成后,用分块上传接口传文件,单块大小设为8MB,支持大文件断点续传,适配AKS网络波动场景。核心实现代码如下:
public async Task UploadZipToS3(string localZipPath, string saveKey, IAzureSettings settings) { var s3Config = new AmazonS3Config { ServiceURL = settings.S3ServiceUrl, ForcePathStyle = settings.S3ForcePathStyle, Timeout = TimeSpan.FromMinutes(30) }; using var client = new AmazonS3Client(settings.S3AccessKey, settings.S3SecretKey, s3Config); using var transferUtil = new TransferUtility(client); var request = new TransferUtilityUploadRequest { FilePath = localZipPath, BucketName = settings.S3BucketName, Key = saveKey, PartSize = 8 * 1024 * 1024, CannedACL = S3CannedACL.Private }; await transferUtil.UploadAsync(request); } - 上传成功后再删除本地ZIP临时文件,上传失败时保留本地文件做重试,避免录制文件丢失。
五、AKS部署注意事项
- 镜像构建时把Linux版本的FFmpeg二进制拷入镜像,添加到PATH环境变量,确保FFmpeg.AutoGen可以正常加载。
- 给Pod挂载emptyDir类型的临时存储卷,把录制临时目录指向该卷,避免容器根文件系统IO性能不足导致丢帧。
- 资源配置按每路1080P视频流预留1核CPU、512M内存计算,避免封装过程中OOM导致录制文件损坏。
- 离开会议时主动Dispose所有音频写入器、视频封装上下文,不要等GC回收,否则会出现MP4文件头未写入、文件无法播放的问题。
内容的提问来源于stack exchange,提问作者Aimeeting
相关产品推荐
相关产品推荐

