Golang实现使用FFmpeg进行视频H264编解码第三部分:将H264解码为YUV
最编程
2024-01-08 10:14:13
...
FFmpeg的源代码和库都是C的代码和库,golang语言使用FFmpeg的接口需要使用cgo对C库的接口进行封装,以便go代码调用,这里使用的是go的第三方库
import (
"github.com/giorgisio/goav/avcodec"
"github.com/giorgisio/goav/avformat"
"github.com/giorgisio/goav/avutil"
... ... ... ... ...
)
3.1 代码逻辑及使用API
H264解码为YUV,整个代码逻辑如下:
- 创建AvformatContext结构体:
func avformat.AvformatAllocContext() *avformat.Context
- 打开输入文件:
func avformat.AvformatOpenInput(ps **avformat.Context, fi string, fmt *avformat.InputFormat, d **avutil.Dictionary) int
- 获取输入文件的视频流信息:
func (*avformat.Context).AvformatFindStreamInfo(d **avutil.Dictionary) int
- 循环查找视频中包含的流信息,直到找到视频类型的流:
func (*avformat.Context).Streams() []*avformat.Stream
func (*avformat.Stream).CodecParameters() *avcodec.AvCodecParameters
func (*avcodec.AvCodecParameters).AvCodecGetType() avcodec.MediaType
- 查找解码器:
func avcodec.AvcodecFindDecoder(id avcodec.CodecId) *avcodec.Codec or
func avcodec.AvcodecFindDecoderByName(name string) *avcodec.Codec
- 配置解码器:
func (*avcodec.Codec).AvcodecAllocContext3() *avcodec.Context
func (*avcodec.Context).AvcodecCopyContext(ctxt2 *avcodec.Context) int
- 打开解码器:
func (*avcodec.Context).AvcodecOpen2(c *avcodec.Codec, d **avcodec.Dictionary) int
- 分配frame和packet结构
func avutil.AvFrameAlloc() *avutil.Frame
func avcodec.AvPacketAlloc() *avcodec.Packet
- 提供packet数据作为解码器的输入,frame接收解码器的输出
func (*avformat.Context).AvReadFrame(pkt *avcodec.Packet)
func (*avcodec.Context).AvcodecSendPacket(packet *avcodec.Packet) int
func (*avcodec.Context).AvcodecReceiveFrame(frame *avcodec.Frame) int
3.2 具体代码实现
package main
import (
"errors"
"fmt"
"os"
"unsafe"
"github.com/giorgisio/goav/avcodec"
"github.com/giorgisio/goav/avformat"
"github.com/giorgisio/goav/avutil"
)
var width int
var height int
func FFmpeg_H264DecodeToYUV(input_filename string, output_filename string) error {
file, _ := os.Create(output_filename)
//创建AvformatContext结构体
Inputformatctx := avformat.AvformatAllocContext()
//打开文件
if avformat.AvformatOpenInput(&Inputformatctx, input_filename, nil, nil) != 0 {
return errors.New("Unable to open input file " + input_filename)
}
//获取视频流信息
if Inputformatctx.AvformatFindStreamInfo(nil) < 0 {
Inputformatctx.AvformatCloseInput()
return errors.New("Error: Couldn't find stream information.")
}
Inputformatctx.AvDumpFormat(0, input_filename, 0)
nCount := 0
//循环查找视频中包含的流信息,直到找到视频类型的流
//记录下来,保存到videoStreamIndex变量中
var i int
for i = 0; i < int(Inputformatctx.NbStreams()); i++ {
switch Inputformatctx.Streams()[i].CodecParameters().AvCodecGetType() {
case avformat.AVMEDIA_TYPE_VIDEO:
// Get a pointer to the codec context for the video stream
pCodecCtxOrig := Inputformatctx.Streams()[i].Codec()
// 查找解码器
pCodec := avcodec.AvcodecFindDecoder(avcodec.CodecId(pCodecCtxOrig.GetCodecId()))
//pCodec := avcodec.AvcodecFindDecoderByName("libx264")
if pCodec == nil {
return errors.New("Unsupported codec!----------")
}
// 配置解码器
pCodecCtx := pCodec.AvcodecAllocContext3()
if pCodecCtx.AvcodecCopyContext((*avcodec.Context)(unsafe.Pointer(pCodecCtxOrig))) != 0 {
return errors.New("Couldn't copy codec context--------------")
}
// 打开解码器
if pCodecCtx.AvcodecOpen2(pCodec, nil) < 0 {
return errors.New("Could not open codec-------------")
}
width := pCodecCtx.Width()
height := pCodecCtx.Height()
pFrameYUV := avutil.AvFrameAlloc()
packet := avcodec.AvPacketAlloc() //分配一个packet
packet.AvNewPacket(int(width * height)) //调整packet的数据
fmt.Println("width:", width)
fmt.Println("height:", height)
for Inputformatctx.AvReadFrame(packet) >= 0 {
// Is this a packet from the video stream?
if packet.StreamIndex() == i {
// 提供原始数据包数据作为解码器的输入
if pCodecCtx.AvcodecSendPacket(packet) >= 0 {
//从解码器返回解码的输出数据
for pCodecCtx.AvcodecReceiveFrame((*avcodec.Frame)(unsafe.Pointer(pFrameYUV))) == 0 {
nCount++
bytes := []byte{}
//y
ptr := uintptr(unsafe.Pointer(avutil.Data(pFrameYUV)[0]))
for j := 0; j < width*height; j++ {
bytes = append(bytes, *(*byte)(unsafe.Pointer(ptr)))
ptr++
}
//u
ptr = uintptr(unsafe.Pointer(avutil.Data(pFrameYUV)[1]))
for j := 0; j < width*height/4; j++ {
bytes = append(bytes, *(*byte)(unsafe.Pointer(ptr)))
ptr++
}
//v
ptr = uintptr(unsafe.Pointer(avutil.Data(pFrameYUV)[2]))
for j := 0; j < width*height/4; j++ {
bytes = append(bytes, *(*byte)(unsafe.Pointer(ptr)))
ptr++
}
//写文件
file.Write(bytes)
}
if nCount == 100 {
break
}
}
}
packet.AvPacketUnref()
}
fmt.Printf("There are %d frames int total.\n", nCount)
// Free the YUV frame
avutil.AvFrameFree(pFrameYUV)
packet.AvFreePacket()
file.Close()
// Close the codecs
pCodecCtx.AvcodecClose()
(*avcodec.Context)(unsafe.Pointer(pCodecCtxOrig)).AvcodecClose()
// 关闭视频文件
Inputformatctx.AvformatCloseInput()
// Stop after saving frames of first video straem
break
default:
return errors.New("Didn't find a video stream")
}
}
return nil
}
func main() {
input_filename := "song.mp4"
output_filename := "1280x720_yuv420p.yuv"
avformat.AvRegisterAll()
//解码视频流数据
FFmpeg_H264DecodeToYUV(input_filename, output_filename)
}
3.3 YUV文件播放
可以使用YUVplayer播放YUV文件,下载地址为YUVplayer播放器
播放时,设置好播放器的Size和Color