通过 cgo 实现 vobsub2pgm 功能

本文主要实现编写 go 语言代码,去调用 mplayer 的 c 语言代码,将 .sub 字幕文件转成 .pgm 格式的图片文件。

相当于使用 go 来改写 这个代码

vobsub.go 内容如下:

package main

// mplayer 代码来源于 https://github.com/ruediger/VobSub2SRT/tree/master/mplayer
// 这个文件相当于用 go 改写示例 VobSub2SRT/src/vobsub2srt.c++

/*
#include <vobsub.h>
#include <stdlib.h>
#include <spudec.h>
#include <mp_msg.h>
*/
import "C"
import (
    "flag"
    "fmt"
    "os"
    "path/filepath"
    "strings"
    "unsafe"
)

var subBaseNmae = flag.String("subName", "", "输入 .sub 字幕文件路径,需要去掉 .sub 和 .idx 文件后缀")

func main() {
    flag.Parse()
    if strings.TrimSpace(*subBaseNmae) == "" {
        println("Please enter subfile")
        os.Exit(1)
    }
    // C 中的 void * 对应 cgo 中的 unsafe.Pointer
    var spu unsafe.Pointer
    // 调用 C.free 时确保要引用 include <stdlib.h>
    defer C.free(spu)
    // void mp_msg_init(void);
    C.mp_msg_init()
    subBaseDir, _ := filepath.Abs(filepath.Dir(*subBaseNmae))
    // char * 对应 cgo 中的 C.CString
    subname := C.CString(*subBaseNmae)
    // 对申请的非 go 类型,都要手动进行回收
    defer C.free(unsafe.Pointer(subname))

    // void *vobsub_open(const char *subname, const char *const ifo, const int force, unsigned int y_threshold, void** spu);
    // void** 对应 cgo 中直接取 unsafe.Pointer 地址,即 &spu
    vob := C.vobsub_open(subname, C.CString(""), C.int(1), C.uint(0), &spu)
    defer C.free(vob)

    var packet unsafe.Pointer
    defer C.free(packet)

    var timestamp, vlen C.int
    var lastStartPts, width, height, stride, startPts, endPts C.uint
    minWidth := 9
    minHeight := 1
    subCounter := 1

    var image *C.uchar
    defer C.free(unsafe.Pointer(image))
    var imageSize C.size_t

    for {
        // int vobsub_get_packet(void *vobhandle, float pts, void** data, int* timestamp);
        vlen = C.vobsub_get_next_packet(vob, &packet, &timestamp)
        if vlen <= 0 {
            break
        }
        if timestamp < 0 {
            continue
        }
        // void spudec_assemble(void *self, unsigned char *packet, unsigned int len, int pts100);
        // 将 unsafe.Pointer 类型的 packet 转成 unsigned char * --> (*C.uchar)(packet)
        C.spudec_assemble(spu, (*C.uchar)(packet), C.uint(vlen), timestamp)
        // void spudec_heartbeat(void *self, unsigned int pts100);
        C.spudec_heartbeat(spu, C.uint(timestamp))

        // void spudec_get_data(void *self, const unsigned char **image, size_t *image_size, unsigned *width, unsigned *height,
        //  unsigned *stride, unsigned *start_pts, unsigned *end_pts);
        // image 上指向 C.uchar 的指针,所以 unsigned char **image 就只需要对 image 取地址操作
        C.spudec_get_data(spu, &image, &imageSize, &width, &height, &stride, &startPts, &endPts)
        if startPts == lastStartPts {
            continue
        }
        lastStartPts = startPts
        if int(width) < minWidth || int(height) < minHeight {
            continue
        }

        // 这里如何将 *C.uchar 转成 go 的 []bytes
        gobytes := C.GoBytes(unsafe.Pointer(image), C.int(imageSize))

        fname := fmt.Sprintf("pgm_%d.pgm", subCounter)
        fpath := filepath.Join(subBaseDir, fname)
        f, _ := os.Create(fpath)
        f.Write([]byte(fmt.Sprintf("P5\n%d %d %d\n", int(width), int(height), 255)))
        f.Write(gobytes)

        f.Close()
        subCounter++
    }
}

源代码

参考链接:

  • https://karthikkaranth.me/blog/calling-c-code-from-go/
  • http://litang.me/post/golang-cgo/
  • https://golang.org/cmd/cgo/
  • https://jamesadam.me/2016/03/26/c-and-go-dealing-with-void-parameters-in-cgo/
  • https://github.com/golang/go/wiki/cgo