I'm writing an app for the windows platform using FFmpeg and it's golang wrapper goav, but I'm having trouble understanding how to use the C pointers to gain access to an array.
I'm trying to write the frame data, pointed to by a uint8 pointer from C, to a .ppm file in golang.
Once I have this done, for proof of concept that FFmpeg is doing what I expect it to, I want to set the frames to a texture in OpenGl to make a video player with cool transitions; any pointers to do that nice and efficiently would be so very helpful! I'm guessing I need to write some shader code to draw the ppm as a texture...
The PPM file structure looks pretty simple just the header and then a byte of data for each red, green and blue value of each pixel in the frame from top left to bottom right
I'm starting to understanding how to cast the pointers between C and Go types, but how can I access the data and write it in Go with the same result as C? In C I just have to set the pointer offset for the data and state how much of it to write:
for (y = 0; y < height; y++) {
fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
}
I've stripped out all the relevant parts of the C code, the wrapper and my code, shown below:
C code - libavutil/frame.h
#include <stdint.h>
typedef struct AVFrame {
#define AV_NUM_DATA_POINTERS 8
uint8_t *data[AV_NUM_DATA_POINTERS];
int linesize[AV_NUM_DATA_POINTERS];
}
Golang goav wrapper
package avutil
/*
#cgo pkg-config: libavutil
#include <libavutil/frame.h>
#include <stdlib.h>
*/
import "C"
import (
"unsafe"
)
type Frame C.struct_AVFrame
func Data(f *Frame) *uint8 {
return (*uint8)(unsafe.Pointer((*C.uint8_t)(unsafe.Pointer(&f.data))))
}
func Linesize(f *Frame) int {
return int(*(*C.int)(unsafe.Pointer(&f.linesize)))
}
My Golang code
package main
import "github.com/giorgisio/goav/avutil"
func saveFrame(videoFrame *avutil.Frame, width int, height int, iFrame int) {
var szFilename string
var y int
var file *os.File
var err error
szFilename = ""
// Open file
szFilename = fmt.Sprintf("frame%d.ppm", iFrame)
if file, err = os.Create(szFilename); err != nil {
log.Println("Error Reading")
}
// Write header
fh := []byte(fmt.Sprintf("P6
%d %d
255
", width, height))
file.Write(fh)
var b byte = 0
// Write pixel data
for y = 0; y < height; y++ {
d := avutil.Data(videoFrame) // d should be a pointer to the first byte of data
l := avutil.Linesize(videoFrame)
// I'm basically lost trying to figure out how to correctly write
// this to a file, the file is created, but when I open it in GIMP
// the image is mostly black with glitchy fuzz - so it's not being
// written properly; the header seems to be ok, it knows the height
// and width at least.
data := make([]byte, l*3)
ptr := unsafe.Pointer(d)
for i := 0; i < l; i++ {
datum := (*uint8)(unsafe.Pointer(uintptr(ptr) + (uintptr(i)+(uintptr(y)*uintptr(l)))*unsafe.Sizeof(*d)))
data = append(data, *datum)
//fmt.Println(*datum)
}
n, err := file.Write(data)
if err != nil {
log.Println("Error Writing:", szFilename, "-", n)
}
}
file.Close()
}
So, how can I write to a file using a pointer to the data, like you can do in C, and get the same result?
The first frame should be black so all 0's but I'm getting a glitchy fuzz, so it must be accessing some random data
Update: My fix using a C function to save:
package avutil
/*
#cgo pkg-config: libavutil
#include <libavutil/frame.h>
#include <stdlib.h>
#include <stdio.h>
void SaveFrame(const char* location, AVFrame *pFrame, int width, int height) {
FILE *pFile;
int y;
// Open file
pFile=fopen(location, "wb");
if(pFile==NULL)
return;
// Write header
fprintf(pFile, "P6
%d %d
255
", width, height);
// Write pixel data
for(y=0; y<height; y++)
fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
// Close file
fclose(pFile);
}
uint8_t* GetData(AVFrame *pFrame) {
return pFrame->data[0];
}
*/
import "C"
I updated the avutil file, in the goav wrapper package, with this save function at the top, then pass it the frame context so it can get the data pointer from it. I also added this Go function to that avutil file to call the C function
func SaveFrame(location string, f *Frame, width int, height int) {
csLoc := C.CString(location)
C.SaveFrame(csLoc, (*C.struct_AVFrame)(unsafe.Pointer(f)), C.int(width), C.int(height))
C.free(unsafe.Pointer(csLoc))
}