这项研究旨在挖掘 FFmpegKit 的潜力,以实现对设备摄像头的 RTMP 或 RTSP 服务器进行直播。FFmpegKit 提供了一个方便的 FFmpeg 封装,使捕获、编码和传输音频和视频流变得容易。
这同时也是为了检查它与现有的直播套餐(如 Haishinkit)的表现如何
- 从设备的摄像头实时视频和音频流到 RTMP 或 RTSP 服务器。
- 自定义 FFmpeg 命令以满足特定的直播要求。
- 无缝集成 AVCaptureSession 以访问摄像头和麦克风。
- 异步执行以平滑播放在不影响主线程的情况下进行。
我参与了很多直播应用的开发。我一直在使用诸如 HaishinKit 和 LFLiveKit 这样的库。我一直在想,我们是否能在移动应用中通过 ffmpeg 发布直播流。FFmpeg 确实能够将直播流发送到服务器,这是此目的的一个常用工具。FFmpeg 是一个功能强大的多媒体处理工具,能够实时捕获、编码和传输音频和视频。但是我不确定我们是否能在移动端做到这一点。
我正在使用 FFmpeg-kit https://github.com/arthenica/ffmpeg-kit
FFmpeg 的 avfoundation 输入格式允许您使用 AVFoundation 从 macOS 和 iOS 设备捕获视频和音频。
ffmpeg -f avfoundation -i "0:0" -c:v libx264 -c:a aac -f flv rtmp://your-rtmp-server/app/stream
尽管它支持将 avfoundation
作为输入设备,但它本身并不提供摄像头流的预览。avfoundation 更专注于捕获和处理音频和视频数据,而不是渲染实时预览。
在我对在 iOS 上的 ffmpeg 上使用命名管道进行研究时,我发现了一个由 dji_flutter 完成的精彩示例。
您可以创建如下所示的命名管道
let videoPipe = FFmpegKitConfig.registerNewFFmpegPipe()
let audioPipe = FFmpegKitConfig.registerNewFFmpegPipe()
let ffmpegCommand = "-re -f rawvideo -pixel_format bgra -video_size 1920x1080 -framerate 30 -i \(videoPipe!)
-f s16le -ar 48000 -ac 1 -itsoffset -5 -i \(audioPipe!)
-framerate 30 -pixel_format yuv420p -c:v h264 -c:a aac -vf "transpose=1,scale=360:640" -b:v 640k -b:a 64k -vsync 1
-f flv \(url!)"
// Execute FFmpeg command
FFmpegKit.executeAsync(ffmpegCommand) { session in
// Handle FFmpeg execution completion
print("FFmpeg execution completed with return code \(session.returnCode)")
}
写入管道
要写入管道,我们只需简单地使用 FileHandle 并指定管道路径。
if let currentPipe = self.videoPipe, let fileHandle = try? FileHandle(forWritingTo: URL(fileURLWithPath: currentPipe)) {
if #available(iOS 13.4, *) {
try? fileHandle.write(contentsOf: data)
} else {
fileHandle.write(data)
}
fileHandle.closeFile()
} else {
print("Failed to open file handle for writing")
}
由于我没有使用任何类型的缓冲区,输出视频出现了卡顿。使用缓冲区时,ffmpeg 在流传输过程中会退出,因为在缓冲过程中命名管道会达到 EOF。
我查阅了此问题的解决方案,发现了一个这么做的技巧 - https://unix.stackexchange.com/questions/483359/how-can-i-stop-ffmpeg-from-quitting-when-it-reaches-the-end-of-a-named-pipe
我们只需打开命名管道。在 Swift 中,我们可以这样操作
let videoFileDescriptor = open(videoPipe!, O_RDWR)
let audioFileDescriptor = open(audioPipe!, O_RDWR)
这效果非常好!!
let cameraSource = CameraSource(position: .front)
let microphoneSource = MicrophoneSource()
let ffLiveKit = FFLiveKit()
try? ffLiveKit.connect(connection: RTMPConnection(baseUrl: "rtmp://192.168.1.100:1935"))
ffLiveKit.addSource(camera: cameraSource, microphone: microphoneSource)
cameraSource.startPreview(previewView: self.view)
ffLiveKit.prepare(delegate: self)
if !isRecording {
try? ffLiveKit.publish(name: "mystream")
} else {
ffLiveKit.stop()
}
out.mp4
FFmpeg
HaishinKit
- CPU 优化