写点什么

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

用户头像
张音乐
关注
发布于: 2021 年 03 月 29 日
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 filefunc 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
用户头像

张音乐

关注

求你关注我,别不识抬举.别逼我跪下来求你. 2021.03.28 加入

还未添加个人简介

评论

发布
暂无评论
go + ffmpeg + goav 实现拉流解码器