Go
golang
cgo

golangでffmpegやx264つかって変換するプログラムを書いてみる 1日目

More than 1 year has passed since last update.

概要

いままでttLibCというライブラリを書いて、ffmpegのavcodecでデコードしたりx264やopenh264をつかってエンコードしたりしてました。

今回会社の方でgoでプログラムを書く話がでたので、内部用のライブラリをサクッと書いたのですが
社長に公開OKもらったので、作り直して公開したいと考えてます。

建設予定地はこちら
https://github.com/taktod/ttLibGo

使い方は

$ go get github.com/taktod/ttLibGo

で取得できて

func TestAvcodecVideoDecode(t *testing.T) {
    {
        targetCodec := "h264"
        t.Log(targetCodec)
        in, err := os.Open(os.Getenv("HOME") + "/tools/data/source/test.h264.aac.mkv")
        if err != nil {
            panic(err)
        }
        var count uint32
        var reader ttLibGo.Reader
        if !reader.Init("mkv") {
            t.Errorf("reader初期化失敗")
        }
        defer reader.Close()

        var decoder ttLibGoFfmpeg.AvcodecDecoder
        defer decoder.Close()

        for {
            buffer := make([]byte, 65536)
            length, err := in.Read(buffer)
            if err != nil {
                panic(err)
            }
            if !reader.ReadFrame(
                buffer,
                uint64(length),
                func(frame *ttLibGo.Frame) bool {
                    if frame.CodecType == targetCodec {
                        decoder.InitVideo(targetCodec, frame.Width, frame.Height)
                        return decoder.Decode(
                            frame,
                            func(frame *ttLibGo.Frame) bool {
                                count++
                                return true
                            })
                    }
                    return true
                }) {
                t.Errorf("コンテナ読み込み失敗")
                return
            }
            if length < 65536 {
                break
            }
        }
        if count == 0 {
            t.Errorf("デコードされませんでした")
            return
        }
        t.Logf("デコードされた数:%d", count)
    }
}

こんな感じでフレームを取得して変換できます。

とりあえず1日目

今回のプログラムの構成について、まず考えてみた。

ttLibGo/ttLibC
ttLibGo/ttLibGo
ttLibGo/ttLibGoFaac
ttLibGo/ttLibGoFdkaac
ttLibGo/ttLibGoFfmpeg
...

という具合に並べていって

ttLibGo/install.go

をインストールしようとすると、サブディレクトリの部分がそれぞれコンパイルされてインストールされるという方式でいってみようと思います。
なんかgoのgetコマンドでインストールするときに、ライブラリの有無チェックしてコンパイルの処理分岐を書くいい方法がみつからなかったので、
それならいっそ、別々にコンパイルしてもらって、失敗したら放置・・・という形にしました。

c言語のコードの取り扱いについて

submoduleを選択

別でcloneしてコンパイルしてインストールしておいてもらう・・・というコードでもいいのですが、使う側がめんどくさいことになるので、submoduleとして取り込むことにしました。
submoduleはnodeと同じく取得時に勝手にいれてくれるみたいなので、楽でいいですね。

golangでcやc++のコードを扱う場合は、同じディレクトリにファイルがあればコンパイル対象になるみたいです。
submoduleで取得したコードは別のディレクトリにあり、それをコンパイル対象に含めるいい方法がわからなかったので
妙なコードになりますが「ソースコード」をincludeしてみました。

こんな具合
https://github.com/taktod/ttLibGo/blob/29701344cd539ea08b6fc284b534471b42821300/ttLibGoFfmpeg/ttLibC.c

コンパイル時のvisibilityはhiddenに

ライブラリで利用しているコードのvisibilityを強制でhiddenにしてあります。
これをやっておかないと、ttLibGoFfmpeg.aとttLibGo.aを両方取り込むコードを書いたときに定義が重なってエラーになりリンクできないことがあったので、これも苦肉の策です。

goのオブジェクトをもっていくため無理やりuintptrでポインタだけ持っていく

あとgoのポインタをc側にいれて使うために
https://github.com/taktod/ttLibGo/blob/29701344cd539ea08b6fc284b534471b42821300/ttLibGoFfmpeg/avcodecDecoder.go#L111

無理やりuintptr型にしてアドレスだけもっていくということやってます。
危険かもしれませんし、今後のgoの仕様で使えなくなる可能性もあるかもしれませんが
処理でつかってるgoの構造体に同じオブジェクトを保存して使っているので、まぁGCで消されることはないだろうと判断したため、こうしてみました。

別のプログラムでつくったデータをcastしてる形になってる。

これも不安点なのですが、上記のdecodeのコードの場合
ttLibGoでコンパイルしたframe作成処理でC.ttLibC_Frameのメモリーを作成
デコードするときには、ttLibGoFfmpegでコンパイルしたframeの処理のところにキャストして使ってます。
同じマシンでコンパイルしてつかってるので、基本メモリーの配置構成は同じになると思うので問題ないと思うのですが
ちょっと心配です。

コード書いていってみた

ttLibGo

  • frameの取り扱い処理
  • コンテナ読み込み処理
  • コンテナ書き出し処理

ttLibGoFfmpeg

  • libavcodecを利用したデコード処理
  • libswresampleを利用した音声のリサンプル処理
  • libswscaleを利用した映像のリサンプル処理

を書いてみました。
swscaleの処理でメモリーエラーになるバグがあったので、モヤっとしてましたが、それは次の日に原因がわかったので、よかったかな。

2日目 http://qiita.com/taktod/items/278255153b6bba68e95f