优秀的编程知识分享平台

网站首页 > 技术文章 正文

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

nanyue 2024-08-19 18:56:57 技术文章 7 ℃

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


答案2023-03-10:


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


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

go run ./examples/leixiaohua1020/simplest_ffmpeg_picture_encoder/main.go

方法一,参考了[雷霄骅的图像编码器](https://github.com/leixiaohua1020/simplest_ffmpeg_picture_encoder/blob/master/simplest_ffmpeg_picture_encoder/simplest_ffmpeg_picture_encoder.cpp),代码用golang编写。代码如下:

// 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](https://chat.forchange.cn/)生成golang代码:

// 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](https://chatgpt.zcorky.com/)生成golang代码:

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

最近发表
标签列表