天天看點

2023-04-01:當Go語言遇見FFmpeg視訊解碼器,使用Go語言改寫deco

作者:福大大架構師每日一題

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()
}           
2023-04-01:當Go語言遇見FFmpeg視訊解碼器,使用Go語言改寫deco