This commit is contained in:
cfanfrank 2013-03-11 11:02:25 +08:00
commit 4a7ed9cf72
6 changed files with 531 additions and 0 deletions

65
README.md Normal file
View File

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

84
aacdec.go Normal file
View File

@ -0,0 +1,84 @@
package codec
import (
/*
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <string.h>
#include <stdio.h>
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
}

93
aacenc.go Normal file
View File

@ -0,0 +1,93 @@
package codec
import (
/*
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <string.h>
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
}

90
h264dec.go Normal file
View File

@ -0,0 +1,90 @@
package codec
import (
/*
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
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
}

148
h264enc.go Normal file
View File

@ -0,0 +1,148 @@
package codec
import (
/*
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
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
}

51
util.go Normal file
View File

@ -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 <libavutil/avutil.h>
#include <libavformat/avformat.h>
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
}