文章目录
FFmpeg概述
FFmpeg是一个开源的跨平台的音视频处理工具,可以对音频、视频进行转码、裁剪、调节音量、添加水印等操作。
广泛的格式支持。 FFmpeg能够解码、编码、转码、复用、分离、流式传输、过滤和播放几乎人类和机器所创造的任何内容。它支持最古老且晦涩难懂的格式,也支持最前沿的技术。无论这些格式是由标准委员会、社区还是公司设计,都可以得到支持。
高度可移植性。 FFmpeg在各种构建环境、机器架构和配置下,在Linux、Mac OS X、Microsoft Windows以及BSDs和Solaris等操作系统上进行编译运行。
使用方式: FFmpeg可以使用命令行进行操作,也可以通过其他编程语言如Python、C++等进行调用。
FFmpeg使用场景
由于其强大的功能和灵活性,FFmpeg被广泛应用于视频网站、音视频处理、视频监控等领域。
以下是FFmpeg常见的使用场景:
-
视频格式转换 FFmpeg 可以将各种视频格式转换为其他格式,如将 AVI 转为 MP4、将 MKV 转为 FLV 等。
-
音频格式转换 与视频格式转换类似,FFmpeg 也可以将各种音频格式转换为其他格式,如将 MP3 转为 WAV、将 FLAC 转为 AAC 等。
-
合并多个音视频文件 FFmpeg 可以将多个音视频文件合并为一个文件,如将多个 MP4 文件合并为一个 MP4 文件,或将多个 MP3 文件合并为一个文件。
-
剪切、分割视频文件 FFmpeg 可以将视频文件剪切为想要的长度或从中间分割出想要的部分,生成新的视频文件。
-
提取音频、视频 FFmpeg 可以从视频文件中提取出音频或视频,如从 MP4 文件中提取出只包含音频的 MP3 文件。
-
视频水印、字幕添加 FFmpeg 可以在视频中添加水印、字幕等元素,实现对视频内容的修饰或描述。
-
视频压缩 FFmpeg 可以将视频压缩,减小视频文件大小,方便存储和传输。
-
视频处理、滤镜应用 FFmpeg 支持各种视频处理和滤镜应用,如提取视频帧、改变视频大小、色彩和对比度调整等。
go语言中使用FFmpeg
在 Go 语言中,使用 Cgo 调用 FFmpeg 的库即可处理 RTSP 视频流。具体步骤如下:
-
安装 FFmpeg 库。可以通过 FFmpeg 的官网或者它的 Github 页面下载源代码并进行编译安装。
-
导入 FFmpeg 的 C 类型定义和库函数。可以创建一个名为 ffmpeg.go 的文件,该文件包含以下代码:
go
package main
// #cgo pkg-config: libavcodec libavutil libavformat libswscale
// #include <libavcodec/avcodec.h>
// #include <libavutil/imgutils.h>
// #include <libavutil/parseutils.h>
// #include <libavutil/samplefmt.h>
// #include <libavformat/avformat.h>
// #include <libswscale/swscale.h>
import "C"
使用 cgo 工具时,需要使用 #cgo 指令来告诉 Go 编译器需要的 C 代码和库文件。在这种情况下,我们需要导入 libavcodec、libavutil、libavformat 和 libswscale 四个库。
- 编写函数来处理 RTSP 视频流。可以创建一个名为 rtsp.go 的文件,该文件包含以下代码:
go
package main
import (
"fmt"
"log"
"os"
"unsafe"
)
const (
maxAudioFrameSize = 192000
maxVideoFrameSize = 192000
)
type VideoDecoder struct {
context *C.AVCodecContext
codec *C.AVCodec
frame *C.AVFrame
frameRGB *C.AVFrame
buffer *C.uint8_t
packet C.AVPacket
packetSize int
packetPos int
imgConvertCtx *C.SwsContext
width int
height int
}
func NewVideoDecoder() *VideoDecoder {
decoder := &VideoDecoder{}
decoder.codec = C.avcodec_find_decoder(C.AV_CODEC_ID_H264)
if decoder.codec == nil {
log.Fatal("Can't find decoder")
}
decoder.context = C.avcodec_alloc_context3(decoder.codec)
if decoder.context == nil {
log.Fatal("Can't alloc codec context")
}
if C.avcodec_open2(decoder.context, decoder.codec, nil) < 0 {
log.Fatal("Can't open codec")
}
decoder.frame = C.av_frame_alloc()
if decoder.frame == nil {
log.Fatal("Can't alloc frame")
}
decoder.frameRGB = C.av_frame_alloc()
if decoder.frameRGB == nil {
log.Fatal("Can't alloc RGB frame")
}
decoder.imgConvertCtx = C.sws_getContext(
decoder.width, decoder.height, decoder.context.pix_fmt,
decoder.width, decoder.height, CAV_PIX_FMT_RGB24,
C.SWS_BILINEAR, nil, nil, nil,
)
if decoder.imgConvertCtx == nil {
log.Fatal("Can't create image convert context")
}
return decoder
}
func (decoder *VideoDecoder) Decode(packetData []byte) (int, int, []byte) {
if len(packetData) == 0 {
return 0, 0, nil
}
packetDataPtr := unsafe.Pointer(&packetData[0])
packetDataSize := len(packetData)
defer C.av_packet_unref(&decoder.packet)
for packetDataSize > 0 {
packetRemainingSize := C.int(packetDataSize)
packetStart := C.uint8_t(packetDataPtr)
packetEnd := packetStart + packetRemainingSize
packetRemainingData := packetEnd - packetStart
if packetRemainingData > C.int(decoder.packetSize)-decoder.packetPos {
packetRemainingData = C.int(decoder.packetSize) - decoder.packetPos
}
copy(decoder.packet.data[decoder.packetPos:decoder.packetPos+packetRemainingData], C.GoBytes(packetStart, packetRemainingData))
packetDataSize -= int(packetRemainingData)
packetDataPtr = unsafe.Pointer(C.uintptr_t(packetStart) + uintptr(packetRemainingData))
decoder.packetPos += int(packetRemainingData)
if decoder.packetPos >= int(decoder.packetSize) {
frameFinished := C.int(0)
C.avcodec_decode_video2(decoder.context, decoder.frame, &frameFinished, &decoder.packet)
if frameFinished != 0 {
decodedWidth := int(decoder.context.width)
decodedHeight := int(decoder.context.height)
decodedData := make([]byte, decodedWidth*decodedHeight*3)
decodedDataPtr := unsafe.Pointer(&decodedData[0])
convertedHeight := C.sws_scale(
decoder.imgConvertCtx, (**C.uint8_t)(&decoder.frame.data[0]), (*C.int)(unsafe.Pointer(&decoder.frame.linesize[0])),
0, C.int(decoder.height), (**C.uint8_t)(&decodedDataPtr), (*C.int)(unsafe.Pointer(&decodedWidth)),
)
return decodedWidth, int(convertedHeight), decodedData
}
decoder.packetPos = 0
}
}
return 0, 0, nil
}
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
videoDecoder := NewVideoDecoder()
// TODO: 连接 RTSP 视频流并获取数据包进行处理
}
这个代码文件定义了一个 VideoDecoder 结构体,用于处理视频流。它的 Decode 方法接收一个字节数组作为参数,返回解码后的视频帧的宽度、高度和 RGB24 格式的像素数据。
- 使用循环从 RTSP 视频流中读取数据包,并将数据包解码。可以在 main 函数中实现循环,读取 RTSP 视频流中的数据包,如下所示:
go
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
videoDecoder := NewVideoDecoder()
rtspURL := "rtsp://example.com/stream"
rtspOptions := "rtsp_transport=tcp"
// Connect to RTSP stream
formatContext := C.avformat_alloc_context()
cURL := C.CString(rtspURL)
cOptions := C.CString(rtspOptions)
defer func() {
C.avformat_free_context(formatContext)
C.free(unsafe.Pointer(cURL))
C.free(unsafe.Pointer(cOptions))
}()
if C.avformat_open_input(&formatContext, cURL, nil, nil) != 0 {
log.Fatal("Error opening input")
}
if C.avformat_find_stream_info(formatContext, nil) < 0 {
log.Fatal("Error finding stream info")
}
videoStreamIndex := C.int(-1)
for i := C.uint(0); i < formatContext.nb_streams; i++ {
stream := (*C.AVStream)(unsafe.Pointer(formatContext.streams[i]))
if stream.codec.codec_type == C.AVMEDIA_TYPE_VIDEO {
videoStreamIndex = i
break
}
}
if videoStreamIndex == -1 {
log.Fatal("No video stream found")
}
codecContext := (*C.AVCodecContext)(unsafe.Pointer(formatContext.streams[videoStreamIndex].codec))
codec := C.avcodec_find_decoder(codecContext.codec_id)
if codec == nil {
log.Fatal("Can't find decoder")
}
if C.avcodec_open2(codecContext, codec, nil) < 0 {
log.Fatal("Can't open codec")
}
var packet C.AVPacket
packetData := make([]byte, maxVideoFrameSize)
for {
if C.av_read_frame(formatContext, &packet) < 0 {
break
}
if packet.stream_index == videoStreamIndex {
packetSize := int(packet.size)
if packetSize > maxVideoFrameSize {
log.Fatal("Packet too big")
}
copy(packetData, C.GoBytes(unsafe.Pointer(packet.data), C.int(packetSize)))
width, height, data := videoDecoder.Decode(packetData[:packetSize])
// TODO: 处理解码后的视频帧,例如显示在屏幕上
}
C.av_packet_unref(&packet)
}
}
这个代码文件中,我们首先使用 libavformat 库连接到 RTSP 视频流,并通过循环从 RTSP 视频流中读取数据包。然后,我们使用视频流的编解码器(codec)将数据包解码,并将解码后的数据传递给我们先前创建的 VideoDecoder 对象进行进一步处理。
注意,这个代码文件中只实现了视频的解码和显示。如果需要处理 RTSP 视频流的音频,还需要编写类似的代码来实现音频的解码和播放。