欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

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,整个代码逻辑如下:

  1. 创建AvformatContext结构体:
 func avformat.AvformatAllocContext() *avformat.Context
  1. 打开输入文件:
func avformat.AvformatOpenInput(ps **avformat.Context, fi string, fmt *avformat.InputFormat, d **avutil.Dictionary) int
  1. 获取输入文件的视频流信息:
func (*avformat.Context).AvformatFindStreamInfo(d **avutil.Dictionary) int
  1. 循环查找视频中包含的流信息,直到找到视频类型的流:
func (*avformat.Context).Streams() []*avformat.Stream
func (*avformat.Stream).CodecParameters() *avcodec.AvCodecParameters
func (*avcodec.AvCodecParameters).AvCodecGetType() avcodec.MediaType 
  1. 查找解码器:
func avcodec.AvcodecFindDecoder(id avcodec.CodecId) *avcodec.Codec   or
func avcodec.AvcodecFindDecoderByName(name string) *avcodec.Codec 
  1. 配置解码器:
func (*avcodec.Codec).AvcodecAllocContext3() *avcodec.Context 
func (*avcodec.Context).AvcodecCopyContext(ctxt2 *avcodec.Context) int
  1. 打开解码器:
func (*avcodec.Context).AvcodecOpen2(c *avcodec.Codec, d **avcodec.Dictionary) int
  1. 分配frame和packet结构
func avutil.AvFrameAlloc() *avutil.Frame
func avcodec.AvPacketAlloc() *avcodec.Packet
  1. 提供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
在这里插入图片描述