go + ffmpeg + goav 实现拉流解码器
发布于: 2021 年 03 月 29 日
一、功能说明
golang+ffmpeg+goav 实现拉流解码器,支持视频拉流解码并转换为 YUV、BGR24 或 RGB24 等图像像素数据
YUV 在流媒体协议中用的较多(数据少,节省流量带宽),在图像处理应用较多的是 BGR 和 RGB 像素数据。我们已经获取到了 YUV 数据,那么把 YUV 转成 BGR 或者 RGB 需要再进行一次转换
要使用这些工具包之前建议先了解一下 goav,如果以前是从事 Java 工作的,就会对于 maven 很清楚,可以管理 jar 包。go get 命令下载的东西可以理解为 jar 包。java 中调用 ffmpeg 可以使用 javacpp, goav 就相当于 go 语言版本的 javacpp,封装了很多操作 ffmpeg api 的方法,提供给你直接调用。
二、懒人工具包
go get github.com/giorgisio/goav
复制代码
三、功能封装
(1)、初始化类库
// 加载ffmpeg的网络库
avformat.AvRegisterAll()
// 加载ffmpeg的编解码库
avcodec.AvcodecRegisterAll()
log.Printf("AvFilter Version:\t%v", avfilter.AvfilterVersion())
log.Printf("AvDevice Version:\t%v", avdevice.AvdeviceVersion())
log.Printf("SWScale Version:\t%v", swscale.SwscaleVersion())
log.Printf("AvUtil Version:\t%v", avutil.AvutilVersion())
log.Printf("AvCodec Version:\t%v", avcodec.AvcodecVersion())
log.Printf("Resample Version:\t%v", swresample.SwresampleLicense())
复制代码
(2)、打开视频流
/**
打开视频流
*/
func openInput(filename string) *avformat.Context {
//
formatCtx := avformat.AvformatAllocContext()
// 打开视频流
if avformat.AvformatOpenInput(&formatCtx, filename, nil, nil) != 0 {
fmt.Printf("Unable to open file %s\n", filename)
return nil;
}
return formatCtx;
}
复制代码
(3)、检索视频流
/**
检索流信息
*/
func findStreamInfo(ctx *avformat.Context) bool {
if ctx.AvformatFindStreamInfo(nil) < 0 {
log.Println("Error: Couldn't find stream information.")
// 关闭文件,释放 媒体文件/流的上下文
ctx.AvformatCloseInput()
return false
}
return true
}
复制代码
(4)、获取视频帧
/**
获取第一帧视频位置
*/
func findFirstVideoStreamIndex(ctx *avformat.Context) int {
videoStreamIndex := -1;
for index, stream := range ctx.Streams() {
switch stream.CodecParameters().AvCodecGetType() {
case avformat.AVMEDIA_TYPE_VIDEO:
return index
}
}
return videoStreamIndex;
}
/**
读取一帧视频帧
*/
func findFirstVideoStreamCodecContext(ctx *avformat.Context) *avformat.CodecContext {
for _, stream := range ctx.Streams() {
switch stream.CodecParameters().AvCodecGetType() {
case avformat.AVMEDIA_TYPE_VIDEO:
return stream.Codec()
}
}
return nil
}
/**
根据索引获取视频帧
*/
func findVideoStreamCodecContext(ctx *avformat.Context ,videoStreamIndex int) *avformat.CodecContext {
if videoStreamIndex >= 0 {
streams := ctx.Streams()
stream := streams[videoStreamIndex]
return stream.Codec();
}
return nil
}
复制代码
(5)、查找编解码器
/**
查找并打开编解码器
*/
func findAndOpenCodec(codecCtx *avformat.CodecContext) *avcodec.Context {
codec := avcodec.AvcodecFindDecoder(avcodec.CodecId(codecCtx.GetCodecId()))
if codec == nil {
fmt.Println("Unsupported codec!")
return nil
}
codecContext := codec.AvcodecAllocContext3()
if codecContext.AvcodecCopyContext((*avcodec.Context)(unsafe.Pointer(codecCtx))) != 0 {
fmt.Println("Couldn't copy codec context")
return nil
}
if codecContext.AvcodecOpen2(codec, nil) < 0 {
fmt.Println("Could not open codec")
return nil
}
return codecContext
}
复制代码
(6)、循环读取视频帧并转换成 RGB 或 BGR 图像像素数据
for i := 0; i < int(formatCtx.NbStreams()); i++ {
switch formatCtx.Streams()[i].CodecParameters().AvCodecGetType() {
case avformat.AVMEDIA_TYPE_VIDEO:
// 循环读取视频帧并解码成 rgb , 默认就是yuv数据
packet := avcodec.AvPacketAlloc()
frameNumber := 1
for formatCtx.AvReadFrame(packet) >= 0 {
// Is this a packet from the video stream?
if packet.StreamIndex() == i {
// Is this a packet from the video stream?
// Decode video frame
response := codecCtx.AvcodecSendPacket(packet)
if response < 0 {
fmt.Printf("Error while sending a packet to the decoder: %s\n", avutil.ErrorFromCode(response))
}
for response >= 0 {
response = codecCtx.AvcodecReceiveFrame((*avcodec.Frame)(unsafe.Pointer(pFrame)))
if response == avutil.AvErrorEAGAIN || response == avutil.AvErrorEOF {
break
} else if response < 0 {
fmt.Printf("Error while receiving a frame from the decoder: %s\n", avutil.ErrorFromCode(response))
return
}
// 从原生数据转换成RGB, 转换 5 个视频帧
// Convert the image from its native format to RGB
if frameNumber <= 5 {
swscale.SwsScale2(swsCtx, avutil.Data(pFrame),
avutil.Linesize(pFrame), 0, codecCtx.Height(),
avutil.Data(pFrameRGB), avutil.Linesize(pFrameRGB))
// 保存到本地硬盘
fmt.Printf("Writing frame %d\n", frameNumber)
SaveFrame(pFrameRGB, codecCtx.Width(), codecCtx.Height(), frameNumber)
} else {
return
}
frameNumber++
}
// 释放资源
// Free the packet that was allocated by av_read_frame
packet.AvFreePacket()
}
}
// Free the RGB image
avutil.AvFree(buffer)
avutil.AvFrameFree(pFrameRGB)
// Free the YUV frame
avutil.AvFrameFree(pFrame)
// Close the codecs
codecCtx.AvcodecClose()
(*avcodec.Context)(unsafe.Pointer(avCodecCtx)).AvcodecClose()
// Close the video file
formatCtx.AvformatCloseInput()
default:
fmt.Println("Didn't find a video stream")
}
}
复制代码
三、完整代码
package main
import (
"fmt"
"github.com/giorgisio/goav/avcodec"
"github.com/giorgisio/goav/avdevice"
"github.com/giorgisio/goav/avfilter"
"github.com/giorgisio/goav/avformat"
"github.com/giorgisio/goav/avutil"
"github.com/giorgisio/goav/swresample"
"github.com/giorgisio/goav/swscale"
"log"
"os"
"unsafe"
)
// SaveFrame writes a single frame to disk as a PPM file
func SaveFrame(frame *avutil.Frame, width, height, frameNumber int) {
// Open file
fileName := fmt.Sprintf("frame%d.ppm", frameNumber)
file, err := os.Create(fileName)
if err != nil {
log.Println("Error Reading")
}
defer file.Close()
// Write header
header := fmt.Sprintf("P6\n%d %d\n255\n", width, height)
file.Write([]byte(header))
// Write pixel data
for y := 0; y < height; y++ {
data0 := avutil.Data(frame)[0]
buf := make([]byte, width*3)
startPos := uintptr(unsafe.Pointer(data0)) + uintptr(y)*uintptr(avutil.Linesize(frame)[0])
for i := 0; i < width*3; i++ {
element := *(*uint8)(unsafe.Pointer(startPos + uintptr(i)))
buf[i] = element
}
file.Write(buf)
}
}
func main() {
filename := "/home/yinyue/upload/sucai.mp4"
// output := "";
// 加载ffmpeg的网络库
avformat.AvRegisterAll()
// 加载ffmpeg的编解码库
avcodec.AvcodecRegisterAll()
log.Printf("AvFilter Version:\t%v", avfilter.AvfilterVersion())
log.Printf("AvDevice Version:\t%v", avdevice.AvdeviceVersion())
log.Printf("SWScale Version:\t%v", swscale.SwscaleVersion())
log.Printf("AvUtil Version:\t%v", avutil.AvutilVersion())
log.Printf("AvCodec Version:\t%v", avcodec.AvcodecVersion())
log.Printf("Resample Version:\t%v", swresample.SwresampleLicense())
// 打开视频流
formatCtx := openInput(filename)
if formatCtx == nil {
return
}
// 检索流信息
success := findStreamInfo(formatCtx);
if !success {
return
}
fmt.Println("检索流信息: " , success)
// 打印 ffmpeg 的日志
formatCtx.AvDumpFormat(0, filename, 0)
// 读取一帧视频帧
avCodecCtx := findFirstVideoStreamCodecContext(formatCtx)
if avCodecCtx == nil {
fmt.Println("没有发现视频帧: ")
return
}
// 查找并打开解码器
codecCtx := findAndOpenCodec(avCodecCtx)
if codecCtx == nil {
fmt.Println("没有发现解码器,或解码器不可用: ")
return
}
// Allocate video frame
pFrame := avutil.AvFrameAlloc()
// Allocate an AVFrame structure
pFrameRGB := avutil.AvFrameAlloc()
// Determine required buffer size and allocate buffer
numBytes := uintptr(avcodec.AvpictureGetSize(avcodec.AV_PIX_FMT_RGB24, codecCtx.Width(), codecCtx.Height()))
buffer := avutil.AvMalloc(numBytes)
// Assign appropriate parts of buffer to image planes in pFrameRGB
// Note that pFrameRGB is an AVFrame, but AVFrame is a superset
// of AVPicture
avp := (*avcodec.Picture)(unsafe.Pointer(pFrameRGB))
avp.AvpictureFill((*uint8)(buffer), avcodec.AV_PIX_FMT_RGB24, codecCtx.Width(), codecCtx.Height())
// initialize SWS context for software scaling
swsCtx := swscale.SwsGetcontext(
codecCtx.Width(),
codecCtx.Height(),
(swscale.PixelFormat)(codecCtx.PixFmt()),
codecCtx.Width(),
codecCtx.Height(),
avcodec.AV_PIX_FMT_RGB24,
avcodec.SWS_BILINEAR,
nil,
nil,
nil,
)
for i := 0; i < int(formatCtx.NbStreams()); i++ {
switch formatCtx.Streams()[i].CodecParameters().AvCodecGetType() {
case avformat.AVMEDIA_TYPE_VIDEO:
// 循环读取视频帧并解码成 rgb , 默认就是yuv数据
packet := avcodec.AvPacketAlloc()
frameNumber := 1
for formatCtx.AvReadFrame(packet) >= 0 {
// Is this a packet from the video stream?
if packet.StreamIndex() == i {
// Is this a packet from the video stream?
// Decode video frame
response := codecCtx.AvcodecSendPacket(packet)
if response < 0 {
fmt.Printf("Error while sending a packet to the decoder: %s\n", avutil.ErrorFromCode(response))
}
for response >= 0 {
response = codecCtx.AvcodecReceiveFrame((*avcodec.Frame)(unsafe.Pointer(pFrame)))
if response == avutil.AvErrorEAGAIN || response == avutil.AvErrorEOF {
break
} else if response < 0 {
fmt.Printf("Error while receiving a frame from the decoder: %s\n", avutil.ErrorFromCode(response))
return
}
// 从原生数据转换成RGB, 转换 5 个视频帧
// Convert the image from its native format to RGB
if frameNumber <= 5 {
swscale.SwsScale2(swsCtx, avutil.Data(pFrame),
avutil.Linesize(pFrame), 0, codecCtx.Height(),
avutil.Data(pFrameRGB), avutil.Linesize(pFrameRGB))
// 保存到本地硬盘
fmt.Printf("Writing frame %d\n", frameNumber)
SaveFrame(pFrameRGB, codecCtx.Width(), codecCtx.Height(), frameNumber)
} else {
return
}
frameNumber++
}
// 释放资源
// Free the packet that was allocated by av_read_frame
packet.AvFreePacket()
}
}
// Free the RGB image
avutil.AvFree(buffer)
avutil.AvFrameFree(pFrameRGB)
// Free the YUV frame
avutil.AvFrameFree(pFrame)
// Close the codecs
codecCtx.AvcodecClose()
(*avcodec.Context)(unsafe.Pointer(avCodecCtx)).AvcodecClose()
// Close the video file
formatCtx.AvformatCloseInput()
default:
fmt.Println("Didn't find a video stream")
}
}
}
/**
打开视频流
*/
func openInput(filename string) *avformat.Context {
//
formatCtx := avformat.AvformatAllocContext()
// 打开视频流
if avformat.AvformatOpenInput(&formatCtx, filename, nil, nil) != 0 {
fmt.Printf("Unable to open file %s\n", filename)
return nil;
}
return formatCtx;
}
/**
检索流信息
*/
func findStreamInfo(ctx *avformat.Context) bool {
if ctx.AvformatFindStreamInfo(nil) < 0 {
log.Println("Error: Couldn't find stream information.")
// 关闭文件,释放 媒体文件/流的上下文
ctx.AvformatCloseInput()
return false
}
return true
}
/**
获取第一帧视频位置
*/
func findFirstVideoStreamIndex(ctx *avformat.Context) int {
videoStreamIndex := -1;
for index, stream := range ctx.Streams() {
switch stream.CodecParameters().AvCodecGetType() {
case avformat.AVMEDIA_TYPE_VIDEO:
return index
}
}
return videoStreamIndex;
}
/**
读取一帧视频帧
*/
func findFirstVideoStreamCodecContext(ctx *avformat.Context) *avformat.CodecContext {
for _, stream := range ctx.Streams() {
switch stream.CodecParameters().AvCodecGetType() {
case avformat.AVMEDIA_TYPE_VIDEO:
return stream.Codec()
}
}
return nil
}
/**
根据索引获取视频帧
*/
func findVideoStreamCodecContext(ctx *avformat.Context ,videoStreamIndex int) *avformat.CodecContext {
if videoStreamIndex >= 0 {
streams := ctx.Streams()
stream := streams[videoStreamIndex]
return stream.Codec();
}
return nil
}
/**
查找并打开编解码器
*/
func findAndOpenCodec(codecCtx *avformat.CodecContext) *avcodec.Context {
codec := avcodec.AvcodecFindDecoder(avcodec.CodecId(codecCtx.GetCodecId()))
if codec == nil {
fmt.Println("Unsupported codec!")
return nil
}
codecContext := codec.AvcodecAllocContext3()
if codecContext.AvcodecCopyContext((*avcodec.Context)(unsafe.Pointer(codecCtx))) != 0 {
fmt.Println("Couldn't copy codec context")
return nil
}
if codecContext.AvcodecOpen2(codec, nil) < 0 {
fmt.Println("Could not open codec")
return nil
}
return codecContext
}
复制代码
四、运行截图
划线
评论
复制
发布于: 2021 年 03 月 29 日阅读数: 13
版权声明: 本文为 InfoQ 作者【张音乐】的原创文章。
原文链接:【http://xie.infoq.cn/article/ad94359dfab46fe5f11ac6edf】。文章转载请联系作者。
张音乐
关注
求你关注我,别不识抬举.别逼我跪下来求你. 2021.03.28 加入
还未添加个人简介
评论