Как записать в файл в Golang, используя указатель на данные C?

Я пишу приложение для платформы Windows, используя FFmpeg и его goav-оболочку golang, но у меня возникают проблемы с пониманием того, как использовать указатели C для получения доступа к массиву.

Я пытаюсь записать данные кадра, на которые указывает указатель uint8 из C, в файл .ppm в golang.

После того, как я это сделал, для доказательства концепции того, что FFmpeg делает то, что я от него ожидаю, я хочу установить кадры в текстуру в OpenGl, чтобы сделать видеоплеер с классными переходами; любые указатели, чтобы сделать это красиво и эффективно, были бы очень полезны! Я предполагаю, что мне нужно написать код шейдера, чтобы нарисовать ppm как текстуру...

структура файла PPM выглядит довольно просто, только заголовок и байт данных для каждого красное, зеленое и синее значение каждого пикселя в кадре сверху слева направо снизу

Я начинаю понимать, как приводить указатели между типами C и Go, но как я могу получить доступ к данным и записать их в Go с тем же результатом, что и C? В C мне просто нужно установить смещение указателя для данных и указать, сколько их нужно записать:

for (y = 0; y < height; y++) {
    fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
}

Я удалил все соответствующие части кода C, оболочки и моего кода, как показано ниже:

Код C — 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

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

Мой код Голанга

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\n%d %d\n255\n", 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()
}

Итак, как я могу записать в файл, используя указатель на данные, как вы можете сделать в C, и получить тот же результат?

Первый кадр должен быть черным, поэтому все 0, но я получаю глючный пушок, поэтому он должен обращаться к каким-то случайным данным.

Обновление: Мое исправление с использованием функции C для сохранения:

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\n%d %d\n255\n", 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"

Я обновил файл avutil в пакете оболочки goav, добавив эту функцию сохранения вверху, а затем передал ему контекст кадра, чтобы он мог получить из него указатель данных. Я также добавил эту функцию Go в этот файл avutil для вызова функции C.

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

person nevernew    schedule 24.04.2018    source источник
comment
Я заработал, сохранив свое изображение, написав функцию сохранения файла на C и вызвав ее из Go. Хотя все равно было бы интересно посмотреть, как это будет работать. Я думаю, проблема в том, что вызов функции .Data() не возвращает правильную вещь, или мое понимание того, что с этим делать, чтобы получить правильный тип для передачи... что мне действительно нужно сейчас для gl.DrawPixels:/   -  person nevernew    schedule 26.04.2018
comment
Можете ли вы опубликовать, какой код вы использовали? Я собираюсь заняться этим и опубликовать что-нибудь, если я разберусь с этим   -  person Andrew    schedule 27.04.2018
comment
@ Эндрю, конечно, я добавил рабочую функцию сохранения C, которую использую. У этого вопроса есть лучший пример того, что я пытаюсь сделать, я думаю: stackoverflow.com/questions/50046388/   -  person nevernew    schedule 27.04.2018


Ответы (1)


У меня была та же проблема: функция C выделила пространство и дала указатель на это пространство и размер для чтения. Я решил это, выполнив:

buffer := *(*[]byte)(unsafe.Pointer(&content))

это преобразует указатель в массив байтов Golang по адресу содержимого. buffer заполняется данными из os.Read, а функция C обрабатывает данные.

person AKJ    schedule 20.07.2020