Bootstrap

go + ffmpeg + goav 实现拉流解码器

一、功能说明

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
}
 

四、运行截图