cgo 的使用總結

SamEichel 8年前發布 | 12K 次閱讀 Cgo FFmpeg Google Go/Golang開發

背景

最近正在基于機器學習搭建一個多媒體分析平臺,一方面鑒于組內成員多則有近兩年的Go使用經驗,少則也有半年的Go使用經驗,

另一方面由于Go的格式統一、工程系統能力強大,所以選擇Go為主要的開發語言。而對于多媒體分析,第一步就是圖片視頻的編解碼,圖片好說,而視頻就比較難了。普通的編解碼可以使用 exec 調用 ffmpeg ,但要獲取視頻每幀的數據內容,就需要使用 ffmpeg 的API了。通過 github ,我們找到了 go-libav 這個庫,相比其他的 go binding of ffmpeg libraries ,這個庫有以下幾個優點:

  • 支持 ffmpeg 3 ,也支持 ffmpeg 2 ,但已廢棄
  • 更加面向對象的編程方法
  • Go-Style,不是對 ffmpeg API 的簡單封裝,而是以更加go的形式進行封裝
  • 更簡單的垃圾回收

其中第二點和第三點是我最欣賞這個庫的主要原因,相比與其他 ffmpeg 庫的直接封裝, go-libav 庫加入了更多的語言易用性的思考。但是,目前這個庫還在持續的開發中,還存在下面幾個問題:

  • 支持的庫有限,目前只有 avcodec avfilter avformat avutil 這四個庫的一些基礎API
  • 缺少樣例,若沒有使用 ffmpeg API 的經驗,上手較難
  • 單元測試覆蓋率只有32%,有可能測試不充分

我們近期已經為 avutil 擴展了一些功能,正在添加examples和單元測試,后續會提 Merge Request 反饋到這個庫。在使用這個庫的過程中,我們踩了一些 cgo 的坑,在這里總結一下 cgo 的使用方法和注意問題。

cgo 的基礎知識

cgo 可以在 go 中調用 C ,也可以在 C 中調用 go 。但因為 go 和 C 垃圾回收以及使用方式的不同,建議盡量避免使用 cgo 。

使用 cgo 的方法比較 怪異 ,在 go 的源代碼中把 C 代碼作為注釋來寫,并標明依賴的庫文件和路徑,最后使用 import "C" 即可。比如要使用 C 中 stdlib.h 中的 random 函數,可以這么寫:

package main

/*

include <stdlib.h>

*/ import "C" import "fmt"

func main() { rand := int(C.random()) fmt.Println("get random value from C", rand) }</code></pre>

注意,一般使用 import 會把所有要使用的包放在一起,比如:

import (
    "fmt"
    "os"
)

但使用 cgo 是個例外,必須給 import "C" 單獨一行,且必須放在注釋的 C 代碼后面一行。

下面就是 cgo 和 go 對應類型的轉換了。進行類型轉換的目的很簡單,就是為了在 C 中使用C的類型,在go中使用go的類型。

標準類型

go的標準類型轉換為C的標準類型比較簡單,直接使用 C.char , C.schar (signed char) , C.uchar (unsigned char) , C.short , C.ushort (unsigned short) , C.int , C.uint (unsigned int) , C.long , C.ulong (unsigned long) , C.longlong (long long) , C.ulonglong (unsigned long long) , C.float , C.double , C.complexfloat (complex float) 以及 C.complexdouble (complex double) ,這些類型已經可以滿足基本的數值運算了。

例子:要調用一個參數類型為int的C函數,這個函數返回一個int值,在go中需要將返回值做類型轉換才可以使用:

var goInt int
ret := int(C.cfunc(C.int(goInt)))
...

字符串

  • go 字符串轉換為 C 字符串: C.CString(gostr string) ,返回的是 C 中的 *char ,這里返回的 *char 不會被 go 的垃圾回收清理,所以需要自行釋放調,可以這么使用 defer C.free(unsafe.Pointer(cstr)) 。
  • C 字符串轉換為 go 字符串: C.GoString(cstr string) ,返回的是 go 的 string 。還有一個類似的函數,通過設置長度,可以取一段子字符串, C.GoString(cstr *C.char, length C.int) 。

struct/union/enum

  • struct:因為C的結構體和go的結構體字節數和數據分配上不同,所以無法直接轉換,所以在go中都是使用 C.struct_xxx 。比如, C_struct_AVOption
  • union和enum:和 struct 類似,可以使用 C.union_xx 和 C.enum_xx ,比如, C.enum_AVPictureType 。

這樣使用起來確實有些別扭,但是封裝C的代碼時,但遵循一定的方法,也可以讓封裝庫的內部和外部調用都 go-style 。其實方法很簡單,想想如果用go寫 struct 和 enum 時,是怎么寫的?

對于C的 struct ,我們可以新建一個go的 struct ,把 C.struct_xx 作為其中的一個元素,比如:

type PixelFormatDescriptor struct {
    CAVPixFmtDescriptor *C.AVPixFmtDescriptor
}

func NewPixelFormatDescriptorFromC(cCtx unsafe.Pointer) PixelFormatDescriptor { return &PixelFormatDescriptor{CAVPixFmtDescriptor: (C.AVPixFmtDescriptor)(cCtx)} }

func FindPixelFormatDescriptorByPixelFormat(pixelFormat PixelFormat) *PixelFormatDescriptor { cDescriptor := C.av_pix_fmt_desc_get(C.enum_AVPixelFormat(pixelFormat)) if cDescriptor == nil { return nil } return NewPixelFormatDescriptorFromC(unsafe.Pointer(cDescriptor)) }

func (d *PixelFormatDescriptor) Name() string { return C.GoString(d.CAVPixFmtDescriptor.name) }

func (d PixelFormatDescriptor) ComponentCount() int { return int(d.CAVPixFmtDescriptor.nb_components) }</code></pre>

這樣看起來是不是有了 go-style ?可以使用 NewPixelFormatDescriptorFromC 和 FindPixelFormatDescriptorByPixelFormat 這兩個方法創建go的結構體 PixelFormatDescriptor ,后面的調用方法就非常簡單明了了。

注意,這里用到了 unsafe.Pointer 這個類型,你可以把它的作用簡單的理解為C中的 void ,從上面的例子也可以看出,主要用來做類型轉換的。</p>

在上面的例子中,還有這樣一個類型 PixelFormat ,它的定義是

type PixelFormat C.enum_AVPixelFormat

這樣,在后續的傳參和調用時,使用 PixelFormat 會更加簡單些。

同時,我們也可以看到,調用C的結構體中的元素時,也很簡單:

CAVPixFmtDescriptor.nb_components

直接加點就可以訪問其成員。

封裝自定義函數

有了上面的知識,做一些簡單的封裝應該沒有問題,要注意的地方就是類型轉換,尤其是涉及到指針時,更要小心謹慎。如果覺得難以處理,就可以使用自定義函數的方法,把復雜的類型轉換拆解為簡單的函數調用,這時只要注意C代碼的編寫規范就可以了。

總結

以上是自己這段時間使用cgo和閱讀源碼的一些總結,網上有人會說使用cgo很難,其實只是cgo的用法與go有差異,一旦涉及到C,可能就會讓人望而卻步。其實不然,用好cgo有以下幾個方面:

  • 注意類型轉換
  • 注意C string的釋放
  • 注意使用 unsafe.Pointer
  • 如果需要,添加自定義函數,避免過多或復雜的轉換
  • 最后一條,也是最重要的,要對C API熟悉

參考資料

 

來自:http://www.hackcv.com/index.php/archives/105/

 

 本文由用戶 SamEichel 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!