2023-04-01:当Go语言遇见FFmpeg视频解码器,使用Go语言改写decode_video.c文件,提升视频解码效率与开发体验。
Posted 福大大架构师每日一题
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2023-04-01:当Go语言遇见FFmpeg视频解码器,使用Go语言改写decode_video.c文件,提升视频解码效率与开发体验。相关的知识,希望对你有一定的参考价值。
2023-04-01:当Go语言遇见FFmpeg视频解码器,使用Go语言改写decode_video.c文件,提升视频解码效率与开发体验。
答案2023-04-01:
步骤如下:
1.导入必要的依赖库,包括 fmt、os、unsafe 和其它 FFmpeg 库相关的 Go 库。
2.定义一个名为 main0() 的函数,该函数负责视频解码操作。在函数中定义了许多变量,例如文件名、编解码器、解析器、编解码器上下文、文件句柄、AVFrame 等等。
3.通过命令行参数获取输入文件名和输出文件名,并进行一些基本的参数检查。
4.通过调用 AvPacketAlloc() 函数创建一个 AVPacket 对象,用于存储解码后的帧数据。如果创建失败,则退出程序。
5.初始化输入缓冲区 inbuf 并设置结尾填充字节为 0。
6.调用 AvcodecFindDecoder() 函数查找 MPEG-1 视频解码器。如果找不到,则退出程序。
7.调用 AvParserInit() 函数初始化解析器。如果初始化失败,则退出程序。
8.调用 AvCodecAllocContext3() 函数分配一个新的编解码器上下文对象。如果分配失败,则退出程序。
9.调用 AvcodecOpen2() 函数打开编解码器。如果打开失败,则退出程序。
10.打开输入文件,并创建一个 AVFrame 对象。
11.进入循环,读取输入文件并将其分解成视频帧。如果读取失败或读取完毕,则跳出循环。
12.调用 AvParserParse2() 函数将输入缓冲区中的数据解析为视频帧,并存储在 AVPacket 对象中。如果解析失败,则退出程序。
13.如果成功解析到一个视频帧,则调用 decode() 函数对其进行解码并保存到输出文件中。
14.在循环结束后,调用 decode() 函数对剩余的数据进行解码并保存到输出文件中。
15.关闭输入文件句柄、解析器、编解码器上下文和 AVFrame 对象等资源,以避免内存泄漏。
16.定义一个名为 pgm_save() 的函数,该函数用于将视频帧写入 PGM 格式文件。
17.定义一个名为 decode() 的函数,该函数用于对视频帧进行解码并调用 pgm_save() 函数将其写入 PGM 格式文件。
18.定义 main() 函数,该函数将 FFmpeg 库的路径设置为当前目录下的 lib 子目录,并调用 main0() 函数进行视频解码操作。
注意:在 Windows 操作系统中,您可能需要将 FFmpeg 库的可执行文件添加到 PATH 环境变量中,或者使用 SetXXXPath() 函数设置它们的路径,才能够正常运行此代码。
代码见github/moonfdd/ffmpeg-go。
执行命令:
./lib/ffmpeg -i ./resources/big_buck_bunny.mp4 -c:v mpeg1video ./out/big_buck_bunny.mpg
go run ./examples/internalexamples/decode_video/main.go ./out/big_buck_bunny.mpg ./out/ppm/big_buck_bunny.yuv
./lib/ffplay ./out/ppm/big_buck_bunny.yuv-113.ppm
golang代码如下:
package main
import (
"fmt"
"os"
"unsafe"
"github.com/moonfdd/ffmpeg-go/ffcommon"
"github.com/moonfdd/ffmpeg-go/libavcodec"
"github.com/moonfdd/ffmpeg-go/libavutil"
)
func main0() (ret ffcommon.FInt)
var filename, outfilename string
var codec *libavcodec.AVCodec
var parser *libavcodec.AVCodecParserContext
var c *libavcodec.AVCodecContext
var f *os.File
var frame *libavutil.AVFrame
var inbuf [INBUF_SIZE + libavcodec.AV_INPUT_BUFFER_PADDING_SIZE]ffcommon.FUint8T
var data *ffcommon.FUint8T
var data_size ffcommon.FSizeT
var pkt *libavcodec.AVPacket
if len(os.Args) <= 2
fmt.Printf("Usage: %s <input file> <output file>\\nAnd check your input file is encoded by mpeg1video please.\\n", os.Args[0])
os.Exit(0)
filename = os.Args[1]
outfilename = os.Args[2]
pkt = libavcodec.AvPacketAlloc()
if pkt == nil
os.Exit(1)
/* set end of buffer to 0 (this ensures that no overreading happens for damaged MPEG streams) */
//memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);
/* find the MPEG-1 video decoder */
codec = libavcodec.AvcodecFindDecoder(libavcodec.AV_CODEC_ID_MPEG1VIDEO)
if codec == nil
fmt.Printf("Codec not found\\n")
os.Exit(1)
parser = libavcodec.AvParserInit(int32(codec.Id))
if parser == nil
fmt.Printf("parser not found\\n")
os.Exit(1)
c = codec.AvcodecAllocContext3()
if c == nil
fmt.Printf("Could not allocate video codec context\\n")
os.Exit(1)
/* For some codecs, such as msmpeg4 and mpeg4, width and height
MUST be initialized there because this information is not
available in the bitstream. */
/* open it */
if c.AvcodecOpen2(codec, nil) < 0
fmt.Printf("Could not open codec\\n")
os.Exit(1)
var err error
f, err = os.Open(filename)
if err != nil
fmt.Printf("Could not open %s,err = %s\\n", filename, err)
os.Exit(1)
frame = libavutil.AvFrameAlloc()
if frame == nil
fmt.Printf("Could not allocate video frame\\n")
os.Exit(1)
for
/* read raw data from the input file */
var n int
n, err = f.Read(inbuf[:INBUF_SIZE])
if err != nil
break
data_size = uint64(n)
if data_size == 0
break
/* use the parser to split the data into frames */
data = (*byte)(unsafe.Pointer(&inbuf))
for data_size > 0
ret = parser.AvParserParse2(c, &pkt.Data, (*int32)(unsafe.Pointer(&pkt.Size)),
data, int32(data_size), libavutil.AV_NOPTS_VALUE, libavutil.AV_NOPTS_VALUE, 0)
if ret < 0
fmt.Printf("Error while parsing\\n")
os.Exit(1)
data = (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(data)) + uintptr(ret)))
data_size -= uint64(ret)
if pkt.Size != 0
decode(c, frame, pkt, outfilename)
/* flush the decoder */
decode(c, frame, nil, outfilename)
f.Close()
parser.AvParserClose()
libavcodec.AvcodecFreeContext(&c)
libavutil.AvFrameFree(&frame)
libavcodec.AvPacketFree(&pkt)
return 0
const INBUF_SIZE = 4096
func pgm_save(buf ffcommon.FBuf, wrap, xsize, ysize ffcommon.FInt, filename string)
var f *os.File
var i ffcommon.FInt
var err error
f, err = os.Create(filename)
if err != nil
return
f.WriteString(fmt.Sprintf("P5\\n%d %d\\n%d\\n", xsize, ysize, 255))
bytes := []byte
for i = 0; i < ysize; i++
for j := int32(0); j < xsize; j++
bytes = append(bytes, *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(buf)) + uintptr(i*wrap+j))))
f.Write(bytes)
f.Close()
func decode(dec_ctx *libavcodec.AVCodecContext, frame *libavutil.AVFrame, pkt *libavcodec.AVPacket, filename string)
// var buf [1024]byte
var ret ffcommon.FInt
ret = dec_ctx.AvcodecSendPacket(pkt)
if ret < 0
fmt.Printf("Error sending a packet for decoding\\n")
os.Exit(1)
for ret >= 0
ret = dec_ctx.AvcodecReceiveFrame(frame)
if ret == -libavutil.EAGAIN || ret == libavutil.AVERROR_EOF
return
else if ret < 0
fmt.Printf("Error during decoding %d\\n", ret)
os.Exit(1)
fmt.Printf("saving frame %3d\\n", dec_ctx.FrameNumber)
//fflush(stdout)
/* the picture is allocated by the decoder. no need to
free it */
pgm_save(frame.Data[0], frame.Linesize[0],
frame.Width, frame.Height, fmt.Sprintf("%s-%d.ppm", filename, dec_ctx.FrameNumber))
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-56.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()
当ABAP遇见普罗米修斯
Jerry每次在工作场合中同Prometheus(普罗米修斯)打交道时,都会“出戏”,因为这个单词给我的第一印象,并不是用go语言实现的微服务监控利器,而是名导雷德利·斯科特(Ridley Scott)拍摄的科幻大片。
回到现实中来,Prometheus是由SoundCloud开发的监控系统的开源版本,logo是一个燃烧的红色火炬,这应该是有情怀的工程师们向泰坦巨神的后代,偷偷盗走天火,传给人类的普罗米修斯表示的敬意。
2016年,由Google发起的Linux基金会(Cloud Native Computing Foundation,CNCF)将Prometheus纳入其第二大开源项目,在开源社区十分活跃,SAP Kyma也使用了Prometheus作为其监控组件。
Prometheus采用Pull方式获取监控信息,并提供了多维度的数据模型和灵活的数据查询和聚合接口。
那么Prometheus是云原生应用和微服务架构的专属工具么?当然不是。下面,Jerry就用SAP CRM On-Premises为例,介绍ABAP技术栈如何借助Prometheus来实现自定义的日志监控功能。
SAP CRM 有个Fiori应用叫做My Opportunity,是SAP成都研究院Jerry所在的CRM Fiori团队开发维护的。假设我们有这样一个需求,需要监控在指定时间段内,该应用收到的读请求。
我们在Opportunity OData服务的实现里找到了一个BAdI增强,CRM_OPPORTUNITY_ODATA_BD:
所有读请求都会经过这个BAdI,所以在里面实现我们自定义的日志逻辑很合适。
创建一个自定义数据库表,用于记录读请求的明细,包括请求者的用户名,请求日期和请求时间。
BAdI实现的逻辑很简单,依次把字段记录下来,插入数据库表:
接着是在本地安装Prometheus服务器,我安装的是Windows版本,需要先编辑prometheus.yml配置文件,然后启动。
下图是配置文件最核心的部分,定义了Prometheus连接SAP CRM抓取监控数据的规则。第24行和25行维护了SAP CRM系统的用户名和密码,第26行/sap/zcm是CRM上为Prometheus暴露出来的数据采集接口的路径,第28行指定Prometheus服务器每隔2秒钟采集一次数据。第33行定义了CRM ABAP系统的主机名和端口号。有了这些配置信息,Prometheus可以同ABAP Netweaver服务器建立连接并进行周期性的数据抓取。
最后一步,在/sap/zcm这个路径上把我们自定义数据库表里的数据暴露出来:
一个SELECT COUNT(*)搞定:
至此万事俱备了。回到Fiori 应用界面,随便点击几个Opportunity,触发读请求,回到自定义的数据库表,发现已经有一些日志记录在内了。
启动Prometheus服务器,马上就以2秒的时间间隔,往ABAP服务器发起数据查询请求:
localhost:9090打开Prometheus的控制台,能看到从SAP CRM系统实时采集到的读请求个数:
切换到Graph面板,能看到指定时间间隔内的读请求变化趋势,比如下图的横轴是时间点,纵轴是读请求个数,图上的折线表达了过去五分钟之内,读请求数量呈线性增长的趋势。
Prometheus提供的dashboard,提供了各种维度的数据查询和聚合功能。如果对其基本的数据展现界面不满意,可以选择另一款效果更好的开源数据可视化工具Grafana. 下图是Grafana的dashboard:
希望Jerry这个例子可以给大家一些启发:ABAP照样可以借助现代开源工具来实现一些传统ABAP工具难以实现的功能。在Jerry看来,Prometheus完全可以同ABAP的单元测试框架一起协同工作,提高基于ABAP技术栈的应用开发的持续集成和持续交付能力。
感谢阅读。
要获取更多Jerry的原创文章,请关注公众号"汪子熙":
以上是关于2023-04-01:当Go语言遇见FFmpeg视频解码器,使用Go语言改写decode_video.c文件,提升视频解码效率与开发体验。的主要内容,如果未能解决你的问题,请参考以下文章
2023-02-28:moonfdd/ffmpeg-go是用go语言绑定ffmpeg的库,目前是github上最好用的库。请用go语言将yuv文件编码为h264文件。
想要使用ffmpeg来完成如下两个功能: 1.视频截图 2.视频分割功能
2023-03-12:mp3音频解码为pcm,代码用go语言编写,调用moonfdd/ffmpeg-go库。