From 4a7ed9cf72682a456cc483f1eaad2253bdc019b3 Mon Sep 17 00:00:00 2001 From: cfanfrank Date: Mon, 11 Mar 2013 11:02:25 +0800 Subject: [PATCH] first --- README.md | 65 +++++++++++++++++++++++ aacdec.go | 84 ++++++++++++++++++++++++++++++ aacenc.go | 93 +++++++++++++++++++++++++++++++++ h264dec.go | 90 ++++++++++++++++++++++++++++++++ h264enc.go | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++ util.go | 51 ++++++++++++++++++ 6 files changed, 531 insertions(+) create mode 100644 README.md create mode 100644 aacdec.go create mode 100644 aacenc.go create mode 100644 h264dec.go create mode 100644 h264enc.go create mode 100644 util.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..fc2c360 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ + +codec +==== + +Golang aac/h264 encoder and decoder + +H264 encoding example + + w := 400 + h := 400 + var nal [][]byte + + c, _ := codec.NewH264Encoder(w, h, image.YCbCrSubsampleRatio420) + nal = append(nal, c.Header) + + for i := 0; i < 60; i++ { + img := image.NewYCbCr(image.Rect(0,0,w,h), image.YCbCrSubsampleRatio420) + p, _ := c.Encode(img) + if len(p.Data) > 0 { + nal = append(nal, p.Data) + } + } + for { + // flush encoder + p, err := c.Encode(nil) + if err != nil { + break + } + nal = append(nal, p.Data) + } + +H264 decoding example + + dec, err := codec.NewH264Decoder(nal[0]) + for i, n := range nal[1:] { + img, err := dec.Decode(n) + if err == nil { + fp, _ := os.Create(fmt.Sprintf("/tmp/dec-%d.jpg", i)) + jpeg.Encode(fp, img, nil) + fp.Close() + } + } + +AAC encoding example + + var pkts [][]byte + + c, _ := codec.NewAACEncoder() + pkts = append(pkts, c.Header) + + for i := 0; i < 60; i++ { + var sample [8192]byte + p, _ := c.Encode(sample) + if len(p) > 0 { + pkts = append(pkts, p) + } + } + +AAC decoding example + + dec, _ := codec.NewAACDecoder(pkts[0]) + for _, p := range pkts[1:] { + sample, err := dec.Decode(p) + } + diff --git a/aacdec.go b/aacdec.go new file mode 100644 index 0000000..47f4ddd --- /dev/null +++ b/aacdec.go @@ -0,0 +1,84 @@ + +package codec + +import ( + /* + #include + #include + #include + #include + + typedef struct { + AVCodec *c; + AVCodecContext *ctx; + AVFrame *f; + int got; + } aacdec_t ; + + static int aacdec_new(aacdec_t *m, uint8_t *buf, int len) { + m->c = avcodec_find_decoder(CODEC_ID_AAC); + m->ctx = avcodec_alloc_context3(m->c); + m->f = avcodec_alloc_frame(); + m->ctx->extradata = buf; + m->ctx->extradata_size = len; + m->ctx->debug = 0x3; + av_log(m->ctx, AV_LOG_DEBUG, "m %p\n", m); + return avcodec_open2(m->ctx, m->c, 0); + } + + static int aacdec_decode(aacdec_t *m, uint8_t *data, int len) { + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = data; + pkt.size = len; + av_log(m->ctx, AV_LOG_DEBUG, "decode %p\n", m); + return avcodec_decode_audio4(m->ctx, m->f, &m->got, &pkt); + } + */ + "C" + "unsafe" + "errors" +) + +type AACDecoder struct { + m C.aacdec_t +} + +func NewAACDecoder(header []byte) (m *AACDecoder, err error) { + m = &AACDecoder{} + r := C.aacdec_new(&m.m, + (*C.uint8_t)(unsafe.Pointer(&header[0])), + (C.int)(len(header)), + ) + if int(r) < 0 { + err = errors.New("open codec failed") + } + return +} + +func (m *AACDecoder) Decode(data []byte) (sample []byte, err error) { + r := C.aacdec_decode( + &m.m, + (*C.uint8_t)(unsafe.Pointer(&data[0])), + (C.int)(len(data)), + ) + if int(r) < 0 { + err = errors.New("decode failed") + return + } + if int(m.m.got) == 0 { + err = errors.New("no data") + return + } + size := int(m.m.f.linesize[0])*2 + sample = make([]byte, size*2) + for i := 0; i < 2; i++ { + C.memcpy( + unsafe.Pointer(&sample[i*size]), + unsafe.Pointer(m.m.f.data[i]), + (C.size_t)(size), + ) + } + return +} + diff --git a/aacenc.go b/aacenc.go new file mode 100644 index 0000000..0f02812 --- /dev/null +++ b/aacenc.go @@ -0,0 +1,93 @@ + +package codec + +import ( + /* + #include + #include + #include + + typedef struct { + AVCodec *c; + AVCodecContext *ctx; + AVFrame *f; + int got; + uint8_t buf[1024*10]; int size; + int samplerate; int bitrate; + int channels; + } aacenc_t ; + + static int aacenc_new(aacenc_t *m) { + m->c = avcodec_find_encoder(CODEC_ID_AAC); + m->ctx = avcodec_alloc_context3(m->c); + m->ctx->sample_fmt = AV_SAMPLE_FMT_FLTP; + m->ctx->sample_rate = m->samplerate; + m->ctx->bit_rate = m->bitrate; + m->ctx->channels = m->channels; + m->ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; + m->f = avcodec_alloc_frame(); + int r = avcodec_open2(m->ctx, m->c, 0); + av_log(m->ctx, AV_LOG_DEBUG, "extra %d\n", m->ctx->extradata_size); + return r; + } + + static void aacenc_encode(aacenc_t *m) { + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = m->buf; + pkt.size = sizeof(m->buf); + m->f->nb_samples = 1024; + m->f->extended_data = m->f->data; + m->f->linesize[0] = 4096; + avcodec_encode_audio2(m->ctx, &pkt, m->f, &m->got); + av_log(m->ctx, AV_LOG_DEBUG, "got %d size %d\n", m->got, pkt.size); + m->size = pkt.size; + } + */ + "C" + "unsafe" + "errors" +) + +type AACEncoder struct { + m C.aacenc_t + Header []byte +} + +// only supported fltp,stereo,44100khz. If you need other config, it's easy to modify code +func NewAACEncoder() (m *AACEncoder, err error) { + m = &AACEncoder{} + m.m.samplerate = 44100 + m.m.bitrate = 50000 + m.m.channels = 2 + r := C.aacenc_new(&m.m) + if int(r) != 0 { + err = errors.New("open codec failed") + return + } + m.Header = make([]byte, (int)(m.m.ctx.extradata_size)) + C.memcpy( + unsafe.Pointer(&m.Header[0]), + unsafe.Pointer(&m.m.ctx.extradata), + (C.size_t)(len(m.Header)), + ) + return +} + +func (m *AACEncoder) Encode(sample []byte) (ret []byte, err error) { + m.m.f.data[0] = (*C.uint8_t)(unsafe.Pointer(&sample[0])) + m.m.f.data[1] = (*C.uint8_t)(unsafe.Pointer(&sample[4096])) + C.aacenc_encode(&m.m) + if int(m.m.got) == 0 { + err = errors.New("no data") + return + } + ret = make([]byte, (int)(m.m.size)) + C.memcpy( + unsafe.Pointer(&ret[0]), + unsafe.Pointer(&m.m.buf[0]), + (C.size_t)(m.m.size), + ) + return +} + diff --git a/h264dec.go b/h264dec.go new file mode 100644 index 0000000..a4884ca --- /dev/null +++ b/h264dec.go @@ -0,0 +1,90 @@ + +package codec + +import ( + /* + #include + #include + #include + + typedef struct { + AVCodec *c; + AVCodecContext *ctx; + AVFrame *f; + int got; + } h264dec_t ; + + static int h264dec_new(h264dec_t *h, uint8_t *data, int len) { + h->c = avcodec_find_decoder(CODEC_ID_H264); + h->ctx = avcodec_alloc_context3(h->c); + h->f = avcodec_alloc_frame(); + h->ctx->extradata = data; + h->ctx->extradata_size = len; + h->ctx->debug = 0x3; + return avcodec_open2(h->ctx, h->c, 0); + } + + static int h264dec_decode(h264dec_t *h, uint8_t *data, int len) { + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = data; + pkt.size = len; + return avcodec_decode_video2(h->ctx, h->f, &h->got, &pkt); + } + */ + "C" + "unsafe" + "errors" + "image" +) + +type H264Decoder struct { + m C.h264dec_t +} + +func NewH264Decoder(header []byte) (m *H264Decoder, err error) { + m = &H264Decoder{} + r := C.h264dec_new( + &m.m, + (*C.uint8_t)(unsafe.Pointer(&header[0])), + (C.int)(len(header)), + ) + if int(r) < 0 { + err = errors.New("open codec failed") + } + return +} + +func (m *H264Decoder) Decode(nal []byte) (f *image.YCbCr, err error) { + r := C.h264dec_decode( + &m.m, + (*C.uint8_t)(unsafe.Pointer(&nal[0])), + (C.int)(len(nal)), + ) + if int(r) < 0 { + err = errors.New("decode failed") + return + } + if m.m.got == 0 { + err = errors.New("no picture") + return + } + + w := int(m.m.f.width) + h := int(m.m.f.height) + ys := int(m.m.f.linesize[0]) + cs := int(m.m.f.linesize[1]) + + f = &image.YCbCr{ + Y: fromCPtr(unsafe.Pointer(m.m.f.data[0]), ys*h), + Cb: fromCPtr(unsafe.Pointer(m.m.f.data[1]), cs*h/2), + Cr: fromCPtr(unsafe.Pointer(m.m.f.data[2]), cs*h/2), + YStride: ys, + CStride: cs, + SubsampleRatio: image.YCbCrSubsampleRatio420, + Rect: image.Rect(0, 0, w, h), + } + + return +} + diff --git a/h264enc.go b/h264enc.go new file mode 100644 index 0000000..1947591 --- /dev/null +++ b/h264enc.go @@ -0,0 +1,148 @@ + +package codec + +import ( + + /* + #include + #include + #include + #include + #include + #include + #include + + typedef struct { + int w, h; + int pixfmt; + char *preset[2]; + char *profile; + int bitrate; + int got; + AVCodec *c; + AVCodecContext *ctx; + AVFrame *f; + AVPacket pkt; + } h264enc_t; + + static int h264enc_new(h264enc_t *m) { + m->c = avcodec_find_encoder(CODEC_ID_H264); + m->ctx = avcodec_alloc_context3(m->c); + m->ctx->width = m->w; + m->ctx->height = m->w; + m->ctx->bit_rate = m->bitrate; + m->ctx->pix_fmt = m->pixfmt; + m->ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; + m->f = avcodec_alloc_frame(); + return avcodec_open2(m->ctx, m->c, NULL); + } + + */ + "C" + "unsafe" + "image" + "errors" + "strings" + //"log" +) + +type H264Encoder struct { + m C.h264enc_t + Header []byte + Pixfmt image.YCbCrSubsampleRatio + W, H int +} + +func NewH264Encoder( + w, h int, + pixfmt image.YCbCrSubsampleRatio, + opts ...string, +) (m *H264Encoder, err error) { + m = &H264Encoder{} + m.m.w = (C.int)(w) + m.m.h = (C.int)(h) + m.W = w + m.H = h + m.Pixfmt = pixfmt + switch pixfmt { + case image.YCbCrSubsampleRatio444: + m.m.pixfmt = C.PIX_FMT_YUV444P + case image.YCbCrSubsampleRatio422: + m.m.pixfmt = C.PIX_FMT_YUV422P + case image.YCbCrSubsampleRatio420: + m.m.pixfmt = C.PIX_FMT_YUV420P + } + for _, opt := range opts { + a := strings.Split(opt, ",") + switch { + case a[0] == "preset" && len(a) == 3: + m.m.preset[0] = C.CString(a[1]) + m.m.preset[1] = C.CString(a[2]) + case a[0] == "profile" && len(a) == 2: + m.m.profile = C.CString(a[1]) + } + } + r := C.h264enc_new(&m.m) + if int(r) < 0 { + err = errors.New("open encoder failed") + return + } + m.Header = fromCPtr(unsafe.Pointer(m.m.ctx.extradata), (int)(m.m.ctx.extradata_size)) + //m.Header = fromCPtr(unsafe.Pointer(m.m.pps), (int)(m.m.ppslen)) + return +} + +type h264Out struct { + Data []byte + Key bool +} + +func (m *H264Encoder) Encode(img *image.YCbCr) (out h264Out, err error) { + var f *C.AVFrame + if img == nil { + f = nil + } else { + if img.SubsampleRatio != m.Pixfmt { + err = errors.New("image pixfmt not match") + return + } + if img.Rect.Dx() != m.W || img.Rect.Dy() != m.H { + err = errors.New("image size not match") + return + } + f = m.m.f + f.data[0] = (*C.uint8_t)(unsafe.Pointer(&img.Y[0])); + f.data[1] = (*C.uint8_t)(unsafe.Pointer(&img.Cb[0])); + f.data[2] = (*C.uint8_t)(unsafe.Pointer(&img.Cr[0])); + f.linesize[0] = (C.int)(img.YStride); + f.linesize[1] = (C.int)(img.CStride); + f.linesize[2] = (C.int)(img.CStride); + } + + C.av_init_packet(&m.m.pkt) + r := C.avcodec_encode_video2(m.m.ctx, &m.m.pkt, f, &m.m.got) + defer C.av_free_packet(&m.m.pkt) + if int(r) < 0 { + err = errors.New("encode failed") + return + } + if m.m.got == 0 { + err = errors.New("no picture") + return + } + if (m.m.pkt.size == 0) { + err = errors.New("packet size == 0") + return + } + + out.Data = make([]byte, m.m.pkt.size) + C.memcpy( + unsafe.Pointer(&out.Data[0]), + unsafe.Pointer(m.m.pkt.data), + (C.size_t)(m.m.pkt.size), + ) + out.Key = (m.m.pkt.flags & C.AV_PKT_FLAG_KEY) != 0 + + return +} + diff --git a/util.go b/util.go new file mode 100644 index 0000000..ce6b126 --- /dev/null +++ b/util.go @@ -0,0 +1,51 @@ + +/* + +Golang h264,aac decoder/encoder libav wrapper + + d, err = codec.NewAACEncoder() + data, err = d.Encode(samples) + + d, err = codec.NewAACDecoder(aaccfg) + samples, err = d.Decode(data) + + var img *image.YCbCr + d, err = codec.NewH264Encoder(640, 480) + img, err = d.Encode(img) + + d, err = codec.NewH264Decoder(pps) + img, err = d.Decode(nal) +*/ +package codec + +import ( + "unsafe" + "reflect" + + /* + #cgo darwin LDFLAGS: -lavformat -lavutil -lavcodec + + #include + #include + + static void libav_init() { + av_register_all(); + av_log_set_level(AV_LOG_DEBUG); + } + */ + "C" +) + +func init() { + C.libav_init() +} + +func fromCPtr(buf unsafe.Pointer, size int) (ret []uint8) { + hdr := (*reflect.SliceHeader)((unsafe.Pointer(&ret))) + hdr.Cap = size + hdr.Len = size + hdr.Data = uintptr(buf) + return +} + +