前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >2023-03-10:YUV420P像素数据编码为JPEG图片,请用go语言实现。

2023-03-10:YUV420P像素数据编码为JPEG图片,请用go语言实现。

原创
作者头像
福大大架构师每日一题
发布2023-03-10 21:27:46
4000
发布2023-03-10 21:27:46
举报
文章被收录于专栏:福大大架构师每日一题

2023-03-10:YUV420P像素数据编码为JPEG图片,请用go语言实现。

答案2023-03-10:

方法一、使用 github.com/moonfdd/ffmpeg-go 库,基于雷霄骅的代码修改。

方法二、使用golang官方库image/jpeg,yuv420p先转换成rgb,再转换成jpeg。代码是用山寨版的chatgpt生成。

代码语言:shell
复制
go run ./examples/leixiaohua1020/simplest_ffmpeg_picture_encoder/main.go

方法一,参考了雷霄骅的图像编码器,代码用golang编写。代码如下:

代码语言:go
复制
// https://github.com/leixiaohua1020/simplest_ffmpeg_picture_encoder/blob/master/simplest_ffmpeg_picture_encoder/simplest_ffmpeg_picture_encoder.cpp
package main

import (
	"fmt"
	"os"
	"os/exec"
	"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 pFormatCtx *libavformat.AVFormatContext
	var fmt0 *libavformat.AVOutputFormat
	var video_st *libavformat.AVStream
	var pCodecCtx *libavcodec.AVCodecContext
	var pCodec *libavcodec.AVCodec

	var picture_buf *ffcommon.FUint8T
	var picture *libavutil.AVFrame
	var pkt libavcodec.AVPacket
	var y_size ffcommon.FInt
	var got_picture ffcommon.FInt = 0
	var size ffcommon.FInt

	var in_file *os.File                    //YUV source
	var in_w, in_h ffcommon.FInt = 640, 360 //YUV's width and height
	var out_file = "./out/pic.jpg"          //Output file
	in := "./out/pic.yuv"

	//是否存在yuv文件
	_, err := os.Stat(in)
	if err != nil {
		if os.IsNotExist(err) {
			fmt.Println("create yuv file")
			exec.Command("./lib/ffmpeg", "-i", "./resources/big_buck_bunny.mp4", "-pix_fmt", "yuv420p", in, "-y").CombinedOutput()
		}
	}

	in_file, _ = os.Open(in)
	if in_file == nil {
		return -1
	}

	libavformat.AvRegisterAll()

	//Method 1
	pFormatCtx = libavformat.AvformatAllocContext()
	//Guess format
	fmt0 = libavformat.AvGuessFormat("mjpeg", "", "")
	pFormatCtx.Oformat = fmt0
	//Output URL
	if libavformat.AvioOpen(&pFormatCtx.Pb, out_file, libavformat.AVIO_FLAG_READ_WRITE) < 0 {
		fmt.Printf("Couldn't open output file.")
		return -1
	}

	//Method 2. More simple
	//avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file);
	//fmt = pFormatCtx->oformat;

	video_st = pFormatCtx.AvformatNewStream(nil)
	if video_st == nil {
		return -1
	}
	pCodecCtx = video_st.Codec
	pCodecCtx.CodecId = fmt0.VideoCodec
	pCodecCtx.CodecType = libavutil.AVMEDIA_TYPE_VIDEO
	pCodecCtx.PixFmt = libavutil.AV_PIX_FMT_YUVJ420P

	pCodecCtx.Width = in_w
	pCodecCtx.Height = in_h

	pCodecCtx.TimeBase.Num = 1
	pCodecCtx.TimeBase.Den = 25
	//Output some information
	pFormatCtx.AvDumpFormat(0, out_file, 1)

	pCodec = libavcodec.AvcodecFindEncoder(pCodecCtx.CodecId)
	if pCodec == nil {
		fmt.Printf("Codec not found.")
		return -1
	}
	if pCodecCtx.AvcodecOpen2(pCodec, nil) < 0 {
		fmt.Printf("Could not open codec.")
		return -1
	}
	picture = libavutil.AvFrameAlloc()
	picture.Width = pCodecCtx.Width
	picture.Height = pCodecCtx.Height
	picture.Format = pCodecCtx.PixFmt
	size = libavcodec.AvpictureGetSize(pCodecCtx.PixFmt, pCodecCtx.Width, pCodecCtx.Height)
	picture_buf = (*byte)(unsafe.Pointer(libavutil.AvMalloc(uint64(size))))
	if picture_buf == nil {
		return -1
	}
	((*libavcodec.AVPicture)(unsafe.Pointer(picture))).AvpictureFill(picture_buf, pCodecCtx.PixFmt, pCodecCtx.Width, pCodecCtx.Height)

	//Write Header
	pFormatCtx.AvformatWriteHeader(nil)

	y_size = pCodecCtx.Width * pCodecCtx.Height
	pkt.AvNewPacket(y_size * 3)
	//Read YUV
	_, err = in_file.Read(ffcommon.ByteSliceFromByteP(picture_buf, int(y_size*3/2)))
	if err != nil {
		fmt.Printf("Could not read input file.%s", err)
		return -1
	}
	picture.Data[0] = picture_buf                                                                         // Y
	picture.Data[1] = (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(picture_buf)) + uintptr(y_size)))     // U
	picture.Data[2] = (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(picture_buf)) + uintptr(y_size*5/4))) // V

	//Encode
	ret = pCodecCtx.AvcodecEncodeVideo2(&pkt, picture, &got_picture)
	if ret < 0 {
		fmt.Printf("Encode Error.\n")
		return -1
	}
	if got_picture == 1 {
		pkt.StreamIndex = uint32(video_st.Index)
		ret = pFormatCtx.AvWriteFrame(&pkt)
	}

	pkt.AvFreePacket()
	//Write Trailer
	pFormatCtx.AvWriteTrailer()

	fmt.Printf("Encode Successful.\n")

	if video_st != nil {
		video_st.Codec.AvcodecClose()
		libavutil.AvFree(uintptr(unsafe.Pointer(picture)))
		libavutil.AvFree(uintptr(unsafe.Pointer(picture_buf)))
	}
	pFormatCtx.Pb.AvioClose()
	pFormatCtx.AvformatFreeContext()

	in_file.Close()

	exec.Command("./lib/ffplay.exe", out_file).Output()
	if err != nil {
		fmt.Println("play err = ", err)
	}

	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-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
		}
	}

	// go func() {
	// 	time.Sleep(1000)
	// 	exec.Command("./lib/ffplay.exe", "rtmp://localhost/publishlive/livestream").Output()
	// 	if err != nil {
	// 		fmt.Println("play err = ", err)
	// 	}
	// }()

	main0()
}

方法二,用第1个chatgpt生成golang代码:

代码语言:go
复制
// https://chat.forchange.cn/
// YUV420P像素数据编码为JPEG图片,请用go语言实现。

package main

import (
	"bufio"
	"fmt"
	"image"
	"image/color"
	"image/jpeg"
	"os"
)

func main() {
	// 将YUV420P像素数据读入内存
	yuvFile, err := os.Open("./out/pic.yuv")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer yuvFile.Close()
	width := 640
	height := 360
	yuvData := make([]byte, width*height*3/2)
	yuvReader := bufio.NewReader(yuvFile)
	_, err = yuvReader.Read(yuvData)
	if err != nil {
		fmt.Println(err)
		return
	}
	// 将YUV420P像素数据转换为RGB格式的图像
	img := image.NewRGBA(image.Rect(0, 0, width, height))
	for y2 := 0; y2 < height; y2++ {
		for x := 0; x < width; x++ {
			// 从YUV420P像素数据中获取Y、U、V值
			yIndex := y2*width + x
			uIndex := width*height + (y2/2)*(width/2) + x/2
			vIndex := width*height*5/4 + (y2/2)*(width/2) + x/2
			y3 := float64(yuvData[yIndex])
			u := float64(yuvData[uIndex]) - 128.0
			v := float64(yuvData[vIndex]) - 128.0
			// 将YUV值转换为RGB值
			r := y3 + 1.402*v
			g := y3 - 0.344*u - 0.714*v
			b := y3 + 1.772*u
			// 将RGB值限制在0-255范围内
			r = limit(r, 0, 255)
			g = limit(g, 0, 255)
			b = limit(b, 0, 255)
			// 将RGB值保存到图像中
			img.Set(x, y2, color.RGBA{uint8(r), uint8(g), uint8(b), 255})
		}
	}
	// 将图像编码为JPEG图片并保存到文件中
	jpegFile, err := os.Create("./out/pic2.jpg")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer jpegFile.Close()
	err = jpeg.Encode(jpegFile, img, nil)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("JPEG image saved to ./out/pic2.jpg")
}

// 将value限制在min和max之间
func limit(value, min, max float64) float64 {
	if value < min {
		return min
	}
	if value > max {
		return max
	}
	return value
}

方法二,用第2个chatgpt生成golang代码:

代码语言:go
复制
// https://chatgpt.zcorky.com/ 第一次失败,第二次成功
// YUV420P像素数据编码为JPEG图片,请用go语言实现。不要用第三方库
package main

import (
	"bufio"
	"bytes"
	"fmt"
	"image"
	"image/color"
	"image/jpeg"
	"os"
)

func main() {
	// 将YUV420P像素数据读入内存
	yuvFile, err := os.Open("./out/pic.yuv")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer yuvFile.Close()
	width := 640
	height := 360
	yuvData := make([]byte, width*height*3/2)
	yuvReader := bufio.NewReader(yuvFile)
	_, err = yuvReader.Read(yuvData)
	if err != nil {
		fmt.Println(err)
		return
	}

	// 假设您已经读取了 YUV420P 格式的像素数据,并将其存储在 data 变量中
	var yuv []byte = yuvData // YUV420P 数据

	// 创建一个新的 RGBA 图像
	rgbaImg := image.NewRGBA(image.Rect(0, 0, width, height))

	// 将 YUV420P 数据转换为 RGBA 数据
	for i := 0; i < width*height; i++ {
		yi := int(yuv[i])
		ui := int(yuv[width*height+(i/4)])
		vi := int(yuv[width*height+(width*height/4)+(i/4)])

		r := float64(yi) + 1.4065*(float64(vi)-128)
		g := float64(yi) - 0.3455*(float64(ui)-128) - 0.7169*(float64(vi)-128)
		b := float64(yi) + 1.7790*(float64(ui)-128)

		if r < 0 {
			r = 0
		} else if r > 255 {
			r = 255
		}

		if g < 0 {
			g = 0
		} else if g > 255 {
			g = 255
		}

		if b < 0 {
			b = 0
		} else if b > 255 {
			b = 255
		}

		rgbaImg.SetRGBA(i%width, i/width, color.RGBA{
			R: uint8(r),
			G: uint8(g),
			B: uint8(b),
			A: 255,
		})
	}

	// 创建 JPEG 图像文件
	jpgFile, err := os.Create("./out/pic3.jpg")
	if err != nil {
		panic(err)
	}
	defer jpgFile.Close()

	// 使用 image/jpeg 包来编码 JPEG 图像
	buf := new(bytes.Buffer)
	if err := jpeg.Encode(buf, rgbaImg, &jpeg.Options{Quality: 80}); err != nil {
		panic(err)
	}

	_, err = jpgFile.Write(buf.Bytes())
	if err != nil {
		panic(err)
	}
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档