2023-04-12:使用 Go 重写 FFmpeg 的 extract_mvs.c 工具程序,提取视频中的运动矢量信息。

2023-04-12 21:10:32 浏览数 (1)

2023-04-12:使用 Go 重写 FFmpeg 的 extract_mvs.c 工具程序,提取视频中的运动矢量信息。

答案2023-04-12:

主要的过程包括:

  1. 打开输入视频文件并查找视频流信息。
  2. 根据视频流类型打开解码器,并设置解码器参数。
  3. 循环读取视频帧数据。
  4. 对每一帧数据进行解码并提取其中的运动矢量信息。
  5. 输出每个运动矢量的相关参数:帧号、来源、块大小、源位置、目标位置、标志等。

具体的过程实现在 main0 函数中,其中调用了 decode_packet 和 open_codec_context 函数来完成解码和上下文打开的过程。最终输出结果通过 fmt.Printf 函数打印到控制台上。

整个程序的主函数为 main,其中设置了 FFmpeg 库的路径和创建了一个 out 目录用于存放输出结果。

代码见moonfdd/ffmpeg-go库。

命令如下:

代码语言:shell复制
go run ./examples/internalexamples/extract_mvs/main.go ./resources/big_buck_bunny.mp4

golang代码如下:

代码语言:go复制
package main

import (
	"fmt"
	"os"
	"unsafe"

	"github.com/moonfdd/ffmpeg-go/ffcommon"
	"github.com/moonfdd/ffmpeg-go/libavcodec"
	"github.com/moonfdd/ffmpeg-go/libavformat"
	"github.com/moonfdd/ffmpeg-go/libavutil"
)

func main0() (ret ffcommon.FInt) {
	var pkt libavformat.AVPacket

	if len(os.Args) != 2 {
		fmt.Printf("Usage: %s <input video>n", os.Args[0])
		os.Exit(1)
	}
	src_filename = os.Args[1]

	if libavformat.AvformatOpenInput(&fmt_ctx, src_filename, nil, nil) < 0 {
		fmt.Printf("Could not open source file %sn", src_filename)
		os.Exit(1)
	}

	if fmt_ctx.AvformatFindStreamInfo(nil) < 0 {
		fmt.Printf("Could not find stream informationn")
		os.Exit(1)
	}

	open_codec_context(fmt_ctx, libavutil.AVMEDIA_TYPE_VIDEO)

	fmt_ctx.AvDumpFormat(0, src_filename, 0)
	for {
		if video_stream == nil {
			fmt.Printf("Could not find video stream in the input, abortingn")
			ret = 1
			break
		}

		frame = libavutil.AvFrameAlloc()
		if frame == nil {
			fmt.Printf("Could not allocate framen")
			ret = -libavutil.ENOMEM
			break
		}

		fmt.Printf("framenum,source,blockw,blockh,srcx,srcy,dstx,dsty,flagsn")

		/* read frames from the file */
		for fmt_ctx.AvReadFrame(&pkt) >= 0 {
			if pkt.StreamIndex == uint32(video_stream_idx) {
				ret = decode_packet(&pkt)
			}
			pkt.AvPacketUnref()
			if ret < 0 {
				break
			}
		}

		/* flush cached frames */
		decode_packet(nil)
		break
	}
	// end:
	libavcodec.AvcodecFreeContext(&video_dec_ctx)
	libavformat.AvformatCloseInput(&fmt_ctx)
	libavutil.AvFrameFree(&frame)
	if ret < 0 {
		return 1
	} else {
		return 0
	}
}

var fmt_ctx *libavformat.AVFormatContext
var video_dec_ctx *libavcodec.AVCodecContext
var video_stream *libavformat.AVStream
var src_filename string

var video_stream_idx ffcommon.FInt = -1
var frame *libavutil.AVFrame
var video_frame_count ffcommon.FInt

func decode_packet(pkt *libavcodec.AVPacket) ffcommon.FInt {
	ret := video_dec_ctx.AvcodecSendPacket(pkt)
	if ret < 0 {
		fmt.Printf("Error while sending a packet to the decoder: %sn", libavutil.AvErr2str(ret))
		return ret
	}

	for ret >= 0 {
		ret = video_dec_ctx.AvcodecReceiveFrame(frame)
		if ret == -libavutil.EAGAIN || ret == libavutil.AVERROR_EOF {
			break
		} else if ret < 0 {
			fmt.Printf("Error while receiving a frame from the decoder: %sn", libavutil.AvErr2str(ret))
			return ret
		}

		if ret >= 0 {
			var i ffcommon.FInt
			var sd *libavutil.AVFrameSideData

			video_frame_count  
			sd = frame.AvFrameGetSideData(libavutil.AV_FRAME_DATA_MOTION_VECTORS)
			if sd != nil {
				//const AVMotionVector
				// mvs := (*libavutil.AVMotionVector)(unsafe.Pointer(sd.Data))
				var a [2]libavutil.AVMotionVector
				len0 := uintptr(unsafe.Pointer(&a[1])) - uintptr(unsafe.Pointer(&a[0]))
				for i = 0; i < sd.Size/int32(len0); i   {
					mv := (*libavutil.AVMotionVector)(unsafe.Pointer(uintptr(unsafe.Pointer(sd.Data))   len0*uintptr(i)))
					fmt.Printf("%d,-,-,-,M,M,M,M,0x%dn",
						video_frame_count, mv.Source,
						mv.W, mv.H, mv.SrcX, mv.SrcY,
						mv.DstX, mv.DstY, mv.Flags)
				}
			}
			frame.AvFrameUnref()
		}
	}

	return 0
}

func open_codec_context(fmt_ctx *libavformat.AVFormatContext, type0 libavutil.AVMediaType) ffcommon.FInt {
	var ret ffcommon.FInt
	var st *libavformat.AVStream
	var dec_ctx *libavcodec.AVCodecContext
	var dec *libavcodec.AVCodec
	var opts *libavutil.AVDictionary

	ret = fmt_ctx.AvFindBestStream(type0, -1, -1, &dec, 0)
	if ret < 0 {
		fmt.Printf("Could not find %s stream in input file '%s'n",
			libavutil.AvGetMediaTypeString(type0), src_filename)
		return ret
	} else {
		stream_idx := ret
		st = fmt_ctx.GetStream(uint32(stream_idx))

		dec_ctx = dec.AvcodecAllocContext3()
		if dec_ctx == nil {
			fmt.Printf("Failed to allocate codecn")
			return -libavutil.EINVAL
		}

		ret = dec_ctx.AvcodecParametersToContext(st.Codecpar)
		if ret < 0 {
			fmt.Printf("Failed to copy codec parameters to codec contextn")
			return ret
		}

		/* Init the video decoder */
		libavutil.AvDictSet(&opts, "flags2", " export_mvs", 0)
		ret = dec_ctx.AvcodecOpen2(dec, &opts)
		if ret < 0 {
			fmt.Printf("Failed to open %s codecn",
				libavutil.AvGetMediaTypeString(type0))
			return ret
		}

		video_stream_idx = stream_idx
		video_stream = fmt_ctx.GetStream(uint32(video_stream_idx))
		video_dec_ctx = dec_ctx
	}

	return 0
}

func main() {
	os.Setenv("Path", os.Getenv("Path") ";./lib")
	ffcommon.SetAvutilPath("./lib/avutil-56.dll")
	ffcommon.SetAvcodecPath("./lib/avcodec-58.dll")
	ffcommon.SetAvdevicePath("./lib/avdevice-58.dll")
	ffcommon.SetAvfilterPath("./lib/avfilter-7.dll")
	ffcommon.SetAvformatPath("./lib/avformat-58.dll")
	ffcommon.SetAvpostprocPath("./lib/postproc-55.dll")
	ffcommon.SetAvswresamplePath("./lib/swresample-3.dll")
	ffcommon.SetAvswscalePath("./lib/swscale-5.dll")

	genDir := "./out"
	_, err := os.Stat(genDir)
	if err != nil {
		if os.IsNotExist(err) {
			os.Mkdir(genDir, 0777) //  Everyone can read write and execute
		}
	}

	main0()
}

0 人点赞