Search code examples
goffmpegcgo

Go / Cgo - How to access a field of a Cstruct?


I develope an application in Go for transcode an audio file from one format to another one:

I use the goav library that use Cgo to bind the FFmpeg C-libs: https://github.com/giorgisio/goav/


The goav library; package avformat has a typedef that cast the original FFmpeg lib C-Struct AVOutputFormat:

type ( 
   OutputFormat               C.struct_AVOutputFormat
)

In my code i have a variable called outputF of the type OutputFormat that is a C.struct_AVOutputFormat.

The C real AVOutputFormat struct has fields:

name, long_name, mime_type, extensions, audio_codec, video_codec, subtitle_codec,..

and many fields more.

See: https://ffmpeg.org/doxygen/2.6/structAVOutputFormat.html


I verified the situation by fmt.Println(outputF) and reached:

{0x7ffff7f23383 0x7ffff7f23907 0x7ffff7f13c33 0x7ffff7f23383 86017 61 0 128 <nil> 0x7ffff7f8cfa0 <nil> 3344 0x7ffff7e3ec10 0x7ffff7e3f410 0x7ffff7e3ecc0 <nil> 0x7ffff7e3dfc0 <nil> <nil> <nil> <nil> <nil> <nil> 0 0x7ffff7e3e070 0x7ffff7e3e020 <nil>}

The audio codec field is on position 5 and contains 86017

I verified the field name by using the package reflect:

val := reflect.Indirect(reflect.ValueOf(outputF))
fmt.Println(val)
fmt.Println("Fieldname: ", val.Type().Field(4).Name)

Output:
Fieldname:  audio_codec

I try to access the field audio_codec of the original AVOutputFormat using:

fmt.Println(outputF.audio_codec)
ERROR: outputF.audio_codec undefined (cannot refer to unexported field or method audio_codec)


fmt.Println(outputF._audio_codec)
ERROR: outputF._audio_codec undefined (type *avformat.OutputFormat has no field or method _audio_codec)

As i read in the Cgo documentation: Within the Go file, C's struct field names that are keywords in Go can be accessed by prefixing them with an underscore: if x points at a C struct with a field named "type", x._type accesses the field. C struct fields that cannot be expressed in Go, such as bit fields or misaligned data, are omitted in the Go struct, replaced by appropriate padding to reach the next field or the end of the struct.

But I have no idea what im doing wrong.

Edit: Okay for sure no underscore is required as audio_codec is not a keyword in Go. This i understood for now. But still there is the question why im not able to access the CStruct field "audio_codec".


Solution

  • There are some idiosyncrasies to GO/CGO that you're bumping into here:

    type OutputFormat C.struct_AVOutputFormat is a go type declaration, not an alias. It might help to think of it as a thin wrapper rather than an alias. Therefore OutputFormat != C.struct_AVOutputFormat and the fields of C.struct_AVOutputFormat are not exported which is why you're getting the ERROR: outputF.audio_codec undefined (cannot refer to unexported field or method audio_codec)

    If the field was called Audio_codec it would fit go's definition of an exported identifier and we could access it, but it isn't.

    There is a way to get around this, but I'd recommend thinking twice before going ahead with it as it uses unsafe pointers and there are risks that your program may lose portability and/or stability at runtime. This is a good intro to unsafe pointers if you'd like to learn more.

    Now if you're really sure you want to do this, the solution is to convert the pointer to OutputFormat into an unsafe pointer then convert it into a pointer to C.struct_AVOutputFormat. Note that this requires you to load the FFMPEG headers to get the definition of C.struct_AVOutputFormat

    //#cgo pkg-config: libavformat
    //#include <libavformat/avformat.h>
    import "C"
    import (
        "fmt"
        "unsafe"
        "github.com/giorgisio/goav/avformat"
    )
    
    func getOutput() *avformat.OutputFormat {
      // calls out to avformat and gets an OutputFormat back
    }
    
    func main() {
        outputF := getOutput()
        coutput := *(*C.struct_AVOutputFormat)(unsafe.Pointer(outputF))
    
        fmt.Println(coutput.audio_codec) // This should now work
    }
    

    Caveat: I've not tested that the cgo package config and <libavformat/avformat.h> import are correct but this worked with a simple C library I stood up to try it out with.