Compare commits

...

2 Commits

Author SHA1 Message Date
kingecg 3fdc475316 添加构建脚本
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 21:12:45 +08:00
kingecg 8a258d8b60 Initial FFmpeg CGO bindings library structure
- Add CGO configuration with pkg-config for FFmpeg libraries
- Implement FormatContext for input/output operations
- Add Stream and CodecParameters wrappers
- Implement Codec/Context with encode/decode API
- Add Frame and Packet wrappers for media data
- Include error types and FFmpegError
- Add CLI tool and simple-transcode example

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 21:02:47 +08:00
10 changed files with 1251 additions and 0 deletions

83
cmd/ffmpeg-cli/main.go Normal file
View File

@ -0,0 +1,83 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"git.kingecg.top/kingecg/goffmpeg/pkg/ffmpeg"
)
func main() {
inputURL := flag.String("i", "", "Input file or URL")
outputURL := flag.String("o", "", "Output file or URL")
codecName := flag.String("c", "", "Codec name (e.g., libx264, aac)")
flag.Parse()
if *inputURL == "" || *outputURL == "" {
flag.Usage()
os.Exit(1)
}
// Open input
ic := ffmpeg.AllocFormatContext()
defer ic.Free()
if err := ic.OpenInput(*inputURL); err != nil {
log.Fatalf("Failed to open input: %v", err)
}
defer ic.Close()
if err := ic.FindStreamInfo(); err != nil {
log.Fatalf("Failed to find stream info: %v", err)
}
ic.DumpFormat(0, *inputURL, false)
// Find video stream
videoStreams := ic.VideoStreams()
if len(videoStreams) == 0 {
log.Fatal("No video stream found")
}
// Open output context
var ofc *ffmpeg.OutputFormatContext
if *codecName != "" {
codec, err := ffmpeg.FindEncoder(*codecName)
if err != nil {
log.Fatalf("Failed to find encoder: %v", err)
}
of := ffmpeg.GuessFormat("", *outputURL)
if of == nil {
log.Fatalf("Failed to guess format")
}
ofc, err = ffmpeg.AllocOutputContext(*outputURL, of)
if err != nil {
log.Fatalf("Failed to allocate output context: %v", err)
}
stream, err := ofc.AddStream(codec)
if err != nil {
log.Fatalf("Failed to add stream: %v", err)
}
vs := videoStreams[0]
cp := vs.CodecParameters()
stream.SetCodecParameters(cp)
} else {
var err error
ofc, err = ffmpeg.AllocOutputContext(*outputURL, nil)
if err != nil {
log.Fatalf("Failed to allocate output context: %v", err)
}
}
defer ofc.Free()
fmt.Println("Transcoding started...")
fmt.Printf("Input: %s\n", *inputURL)
fmt.Printf("Output: %s\n", *outputURL)
}

14
examples/build.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
set -e
echo "Building goffmpeg examples..."
cd "$(dirname "$0")"
# Build simple-transcode example
echo "Building simple-transcode..."
go build -o bin/simple-transcode ./simple-transcode
echo ""
echo "Build complete!"
echo "Run with: ./bin/simple-transcode <input> <output>"

View File

@ -0,0 +1,75 @@
package main
import (
"fmt"
"log"
"os"
"git.kingecg.top/kingecg/goffmpeg/pkg/ffmpeg"
)
// Simple transcoding example using goffmpeg library
func main() {
if len(os.Args) < 3 {
fmt.Println("Usage: simple-transcode <input> <output>")
fmt.Println("Example: simple-transcode input.mp4 output.flv")
os.Exit(1)
}
inputURL := os.Args[1]
outputURL := os.Args[2]
// Open input file
ic := ffmpeg.AllocFormatContext()
defer ic.Free()
if err := ic.OpenInput(inputURL); err != nil {
log.Fatalf("Failed to open input %s: %v", inputURL, err)
}
defer ic.Close()
// Find stream info
if err := ic.FindStreamInfo(); err != nil {
log.Fatalf("Failed to find stream info: %v", err)
}
// Dump input format info
fmt.Printf("Input: %s\n", inputURL)
ic.DumpFormat(0, inputURL, false)
// Find video stream
videoStreams := ic.VideoStreams()
if len(videoStreams) == 0 {
log.Fatal("No video stream found in input")
}
vs := videoStreams[0]
fmt.Printf("Video stream index: %d\n", vs.Index())
// Get codec parameters
cp := vs.CodecParameters()
fmt.Printf("Codec type: %d, Codec ID: %d\n", cp.CodecType(), cp.CodecID())
// Create output context
of := ffmpeg.GuessFormat("", outputURL)
if of == nil {
log.Fatalf("Failed to guess output format")
}
ofc, err := ffmpeg.AllocOutputContext(outputURL, of)
if err != nil {
log.Fatalf("Failed to allocate output context: %v", err)
}
defer ofc.Free()
// Copy stream from input to output
_, err = ofc.AddStream(nil)
if err != nil {
log.Fatalf("Failed to add stream: %v", err)
}
fmt.Printf("\nOutput: %s\n", outputURL)
fmt.Println("Transcoding setup complete. Use the library APIs to process frames.")
_ = vs // vs is used for reference
}

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module git.kingecg.top/kingecg/goffmpeg
//go:build cgo
go 1.25.1

12
pkg/ffmpeg/cgo.go Normal file
View File

@ -0,0 +1,12 @@
package ffmpeg
/*
#cgo pkg-config: libavcodec libavformat libavutil libavfilter libswscale libswresample
#cgo LDFLAGS: -lavcodec -lavformat -lavutil -lavfilter -lswscale -lswresample
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <stdlib.h>
*/
import "C"

288
pkg/ffmpeg/codec.go Normal file
View File

@ -0,0 +1,288 @@
package ffmpeg
/*
#include <libavcodec/avcodec.h>
static enum AVMediaType codec_get_type(const AVCodec *c) {
return c->type;
}
*/
import "C"
import "unsafe"
// CodecType represents the type of codec
type CodecType int
const (
CodecTypeVideo CodecType = CodecType(C.AVMEDIA_TYPE_VIDEO)
CodecTypeAudio CodecType = CodecType(C.AVMEDIA_TYPE_AUDIO)
CodecTypeSubtitle CodecType = CodecType(C.AVMEDIA_TYPE_SUBTITLE)
)
// Codec represents an FFmpeg codec
type Codec struct {
ptr *C.AVCodec
}
// FindDecoder finds a decoder by name
func FindDecoder(name string) (*Codec, error) {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ptr := C.avcodec_find_decoder_by_name(cName)
if ptr == nil {
return nil, ErrCodecNotFound
}
return &Codec{ptr: ptr}, nil
}
// FindEncoder finds an encoder by name
func FindEncoder(name string) (*Codec, error) {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ptr := C.avcodec_find_encoder_by_name(cName)
if ptr == nil {
return nil, ErrCodecNotFound
}
return &Codec{ptr: ptr}, nil
}
// CodecFromC converts a C pointer to Codec
func CodecFromC(ptr *C.AVCodec) *Codec {
return &Codec{ptr: ptr}
}
// CPtr returns the underlying C pointer
func (c *Codec) CPtr() *C.AVCodec {
return c.ptr
}
// Name returns the codec name
func (c *Codec) Name() string {
if c.ptr == nil {
return ""
}
return C.GoString(c.ptr.name)
}
// LongName returns the codec long name
func (c *Codec) LongName() string {
if c.ptr == nil {
return ""
}
return C.GoString(c.ptr.long_name)
}
// Type returns the codec type
func (c *Codec) Type() CodecType {
if c.ptr == nil {
return CodecType(0)
}
t := C.codec_get_type(c.ptr)
return CodecType(t)
}
// ID returns the codec ID
func (c *Codec) ID() int {
if c.ptr == nil {
return 0
}
return int(c.ptr.id)
}
// Context represents a codec context
type Context struct {
ptr *C.AVCodecContext
}
// AllocContext allocates a codec context
func AllocContext() *Context {
return &Context{
ptr: C.avcodec_alloc_context3(nil),
}
}
// Free frees the codec context
func (c *Context) Free() {
if c.ptr != nil {
C.avcodec_free_context(&c.ptr)
c.ptr = nil
}
}
// ContextFromC converts a C pointer to Context
func ContextFromC(ptr *C.AVCodecContext) *Context {
return &Context{ptr: ptr}
}
// CPtr returns the underlying C pointer
func (c *Context) CPtr() *C.AVCodecContext {
return c.ptr
}
// SetCodec sets the codec for this context
func (c *Context) SetCodec(codec *Codec) error {
if c.ptr == nil || codec == nil || codec.ptr == nil {
return ErrInvalidCodec
}
C.avcodec_free_context(&c.ptr)
c.ptr = C.avcodec_alloc_context3(codec.ptr)
if c.ptr == nil {
return ErrInvalidCodec
}
return nil
}
// Width returns the width
func (c *Context) Width() int {
if c.ptr == nil {
return 0
}
return int(c.ptr.width)
}
// SetWidth sets the width
func (c *Context) SetWidth(width int) {
if c.ptr != nil {
c.ptr.width = C.int(width)
}
}
// Height returns the height
func (c *Context) Height() int {
if c.ptr == nil {
return 0
}
return int(c.ptr.height)
}
// SetHeight sets the height
func (c *Context) SetHeight(height int) {
if c.ptr != nil {
c.ptr.height = C.int(height)
}
}
// Format returns the pixel/sample format
func (c *Context) Format() int {
if c.ptr == nil {
return -1
}
return int(c.ptr.sample_fmt)
}
// SetFormat sets the pixel/sample format
func (c *Context) SetFormat(format int) {
if c.ptr != nil {
// Use unsafe.Pointer to assign to the enum field
*(*C.int)(unsafe.Pointer(&c.ptr.sample_fmt)) = C.int(format)
}
}
// BitRate returns the bit rate
func (c *Context) BitRate() int64 {
if c.ptr == nil {
return 0
}
return int64(c.ptr.bit_rate)
}
// SetBitRate sets the bit rate
func (c *Context) SetBitRate(br int64) {
if c.ptr != nil {
c.ptr.bit_rate = C.int64_t(br)
}
}
// Open opens the codec
func (c *Context) Open(codec *Codec) error {
if c.ptr == nil || codec == nil || codec.ptr == nil {
return ErrInvalidCodec
}
ret := C.avcodec_open2(c.ptr, codec.ptr, nil)
if ret < 0 {
return &FFmpegError{
Code: int(ret),
Message: "failed to open codec",
Op: "Open",
}
}
return nil
}
// SendPacket sends a packet to the decoder
func (c *Context) SendPacket(pkt *Packet) error {
if c.ptr == nil {
return ErrInvalidCodec
}
ret := C.avcodec_send_packet(c.ptr, pkt.CPtr())
if ret < 0 {
return &FFmpegError{
Code: int(ret),
Message: "failed to send packet",
Op: "SendPacket",
}
}
return nil
}
// ReceiveFrame receives a frame from the decoder
func (c *Context) ReceiveFrame(frame *Frame) error {
if c.ptr == nil {
return ErrInvalidCodec
}
ret := C.avcodec_receive_frame(c.ptr, frame.CPtr())
if ret < 0 {
return &FFmpegError{
Code: int(ret),
Message: "failed to receive frame",
Op: "ReceiveFrame",
}
}
return nil
}
// SendFrame sends a frame to the encoder
func (c *Context) SendFrame(frame *Frame) error {
if c.ptr == nil {
return ErrInvalidCodec
}
ret := C.avcodec_send_frame(c.ptr, frame.CPtr())
if ret < 0 {
return &FFmpegError{
Code: int(ret),
Message: "failed to send frame",
Op: "SendFrame",
}
}
return nil
}
// ReceivePacket receives a packet from the encoder
func (c *Context) ReceivePacket(pkt *Packet) error {
if c.ptr == nil {
return ErrInvalidCodec
}
ret := C.avcodec_receive_packet(c.ptr, pkt.CPtr())
if ret < 0 {
return &FFmpegError{
Code: int(ret),
Message: "failed to receive packet",
Op: "ReceivePacket",
}
}
return nil
}
// Close closes the codec
func (c *Context) Close() {
if c.ptr != nil {
C.avcodec_close(c.ptr)
}
}

48
pkg/ffmpeg/errors.go Normal file
View File

@ -0,0 +1,48 @@
package ffmpeg
import "errors"
var (
ErrInvalidInput = errors.New("invalid input")
ErrInvalidOutput = errors.New("invalid output")
ErrCodecNotFound = errors.New("codec not found")
ErrFormatNotSupported = errors.New("format not supported")
ErrDecodeFailed = errors.New("decode failed")
ErrEncodeFailed = errors.New("encode failed")
ErrFilterFailed = errors.New("filter failed")
ErrIOFailed = errors.New("I/O operation failed")
ErrNoStream = errors.New("no stream found")
ErrInvalidCodec = errors.New("invalid codec")
)
// FFmpegError wraps FFmpeg errors with additional context
type FFmpegError struct {
Code int
Message string
Op string
}
func (e *FFmpegError) Error() string {
return e.Op + ": " + e.Message + " (code: " + itoa(e.Code) + ")"
}
func itoa(n int) string {
if n < 0 {
return "-" + uitoa(uint(-n))
}
return uitoa(uint(n))
}
func uitoa(n uint) string {
if n == 0 {
return "0"
}
var buf [20]byte
i := len(buf)
for n > 0 {
i--
buf[i] = byte('0' + n%10)
n /= 10
}
return string(buf[i:])
}

497
pkg/ffmpeg/ffmpeg.go Normal file
View File

@ -0,0 +1,497 @@
package ffmpeg
/*
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
*/
import "C"
import "unsafe"
// FormatContext represents the input/output format context
type FormatContext struct {
ptr *C.AVFormatContext
}
// AllocFormatContext allocates a format context
func AllocFormatContext() *FormatContext {
return &FormatContext{
ptr: C.avformat_alloc_context(),
}
}
// Free frees the format context
func (fc *FormatContext) Free() {
if fc.ptr != nil {
C.avformat_close_input(&fc.ptr)
fc.ptr = nil
}
}
// FormatContextFromC converts a C pointer to FormatContext
func FormatContextFromC(ptr *C.AVFormatContext) *FormatContext {
return &FormatContext{ptr: ptr}
}
// CPtr returns the underlying C pointer
func (fc *FormatContext) CPtr() *C.AVFormatContext {
return fc.ptr
}
// OpenInput opens an input file
func (fc *FormatContext) OpenInput(url string) error {
if fc.ptr == nil {
fc.ptr = C.avformat_alloc_context()
if fc.ptr == nil {
return ErrInvalidInput
}
}
cURL := C.CString(url)
defer C.free(unsafe.Pointer(cURL))
ret := C.avformat_open_input(&fc.ptr, cURL, nil, nil)
if ret < 0 {
return &FFmpegError{
Code: int(ret),
Message: "failed to open input",
Op: "OpenInput",
}
}
return nil
}
// Close closes the input
func (fc *FormatContext) Close() {
if fc.ptr != nil {
C.avformat_close_input(&fc.ptr)
}
}
// FindStreamInfo finds stream info
func (fc *FormatContext) FindStreamInfo() error {
if fc.ptr == nil {
return ErrInvalidInput
}
ret := C.avformat_find_stream_info(fc.ptr, nil)
if ret < 0 {
return &FFmpegError{
Code: int(ret),
Message: "failed to find stream info",
Op: "FindStreamInfo",
}
}
return nil
}
// NbStreams returns the number of streams
func (fc *FormatContext) NbStreams() int {
if fc.ptr == nil {
return 0
}
return int(fc.ptr.nb_streams)
}
// Streams returns the streams
func (fc *FormatContext) Streams() []*Stream {
if fc.ptr == nil {
return nil
}
streams := make([]*Stream, fc.NbStreams())
ptrSize := unsafe.Sizeof(fc.ptr.streams)
for i := 0; i < fc.NbStreams(); i++ {
streamPtr := (**C.AVStream)(unsafe.Pointer(uintptr(unsafe.Pointer(fc.ptr.streams)) + uintptr(i)*ptrSize))
streams[i] = StreamFromC(*streamPtr)
}
return streams
}
// VideoStreams returns only video streams
func (fc *FormatContext) VideoStreams() []*Stream {
streams := fc.Streams()
videoStreams := make([]*Stream, 0)
for _, s := range streams {
if s.Type() == CodecTypeVideo {
videoStreams = append(videoStreams, s)
}
}
return videoStreams
}
// AudioStreams returns only audio streams
func (fc *FormatContext) AudioStreams() []*Stream {
streams := fc.Streams()
audioStreams := make([]*Stream, 0)
for _, s := range streams {
if s.Type() == CodecTypeAudio {
audioStreams = append(audioStreams, s)
}
}
return audioStreams
}
// ReadPacket reads a packet
func (fc *FormatContext) ReadPacket(pkt *Packet) error {
if fc.ptr == nil {
return ErrInvalidInput
}
ret := C.av_read_frame(fc.ptr, pkt.CPtr())
if ret < 0 {
return &FFmpegError{
Code: int(ret),
Message: "failed to read packet",
Op: "ReadPacket",
}
}
return nil
}
// WritePacket writes a packet
func (fc *FormatContext) WritePacket(pkt *Packet) error {
if fc.ptr == nil {
return ErrInvalidOutput
}
ret := C.av_interleaved_write_frame(fc.ptr, pkt.CPtr())
if ret < 0 {
return &FFmpegError{
Code: int(ret),
Message: "failed to write packet",
Op: "WritePacket",
}
}
return nil
}
// DumpFormat dumps format info
func (fc *FormatContext) DumpFormat(idx int, url string, isOutput bool) {
if fc.ptr == nil {
return
}
cURL := C.CString(url)
defer C.free(unsafe.Pointer(cURL))
C.av_dump_format(fc.ptr, C.int(idx), cURL, C.int(boolToInt(isOutput)))
}
func boolToInt(b bool) int {
if b {
return 1
}
return 0
}
// Stream represents a media stream
type Stream struct {
ptr *C.AVStream
}
// StreamFromC converts a C pointer to Stream
func StreamFromC(ptr *C.AVStream) *Stream {
return &Stream{ptr: ptr}
}
// CPtr returns the underlying C pointer
func (s *Stream) CPtr() *C.AVStream {
return s.ptr
}
// Index returns the stream index
func (s *Stream) Index() int {
if s.ptr == nil {
return -1
}
return int(s.ptr.index)
}
// Type returns the codec type
func (s *Stream) Type() CodecType {
if s.ptr == nil {
return CodecType(0)
}
return CodecType(s.ptr.codecpar.codec_type)
}
// CodecParameters returns the codec parameters
func (s *Stream) CodecParameters() *CodecParameters {
if s.ptr == nil {
return nil
}
return CodecParametersFromC(s.ptr.codecpar)
}
// SetCodecParameters sets the codec parameters
func (s *Stream) SetCodecParameters(cp *CodecParameters) error {
if s.ptr == nil || cp == nil || cp.ptr == nil {
return ErrInvalidCodec
}
ret := C.avcodec_parameters_copy(s.ptr.codecpar, cp.ptr)
if ret < 0 {
return &FFmpegError{
Code: int(ret),
Message: "failed to copy codec parameters",
Op: "SetCodecParameters",
}
}
return nil
}
// Codec returns the codec context (deprecated: use CodecParameters instead)
func (s *Stream) Codec() *Context {
// In FFmpeg 4.0+, codec field was removed from AVStream
// Use CodecParameters() and allocate a new context if needed
return nil
}
// TimeBase returns the time base
func (s *Stream) TimeBase() Rational {
if s.ptr == nil {
return Rational{}
}
return Rational{
num: int(s.ptr.time_base.num),
den: int(s.ptr.time_base.den),
}
}
// SetTimeBase sets the time base
func (s *Stream) SetTimeBase(r Rational) {
if s.ptr != nil {
s.ptr.time_base.num = C.int(r.num)
s.ptr.time_base.den = C.int(r.den)
}
}
// Rational represents a rational number
type Rational struct {
num int
den int
}
// NewRational creates a new rational
func NewRational(num, den int) Rational {
return Rational{num: num, den: den}
}
// CodecParameters represents codec parameters
type CodecParameters struct {
ptr *C.AVCodecParameters
}
// CodecParametersFromC converts a C pointer to CodecParameters
func CodecParametersFromC(ptr *C.AVCodecParameters) *CodecParameters {
return &CodecParameters{ptr: ptr}
}
// CPtr returns the underlying C pointer
func (cp *CodecParameters) CPtr() *C.AVCodecParameters {
return cp.ptr
}
// CodecType returns the codec type
func (cp *CodecParameters) CodecType() CodecType {
if cp.ptr == nil {
return CodecType(0)
}
return CodecType(cp.ptr.codec_type)
}
// CodecID returns the codec ID
func (cp *CodecParameters) CodecID() int {
if cp.ptr == nil {
return 0
}
return int(cp.ptr.codec_id)
}
// Width returns the width
func (cp *CodecParameters) Width() int {
if cp.ptr == nil {
return 0
}
return int(cp.ptr.width)
}
// Height returns the height
func (cp *CodecParameters) Height() int {
if cp.ptr == nil {
return 0
}
return int(cp.ptr.height)
}
// Format returns the format
func (cp *CodecParameters) Format() int {
if cp.ptr == nil {
return -1
}
return int(cp.ptr.format)
}
// SampleRate returns the sample rate
func (cp *CodecParameters) SampleRate() int {
if cp.ptr == nil {
return 0
}
return int(cp.ptr.sample_rate)
}
// Channels returns the number of channels
func (cp *CodecParameters) Channels() int {
if cp.ptr == nil {
return 0
}
return int(cp.ptr.channels)
}
// FrameSize returns the frame size
func (cp *CodecParameters) FrameSize() int {
if cp.ptr == nil {
return 0
}
return int(cp.ptr.frame_size)
}
// BitRate returns the bit rate
func (cp *CodecParameters) BitRate() int64 {
if cp.ptr == nil {
return 0
}
return int64(cp.ptr.bit_rate)
}
// OutputFormatContext represents an output format context
type OutputFormatContext struct {
FormatContext
}
// AllocOutputContext allocates an output format context
func AllocOutputContext(url string, fmt *OutputFormat) (*OutputFormatContext, error) {
ofc := &OutputFormatContext{
FormatContext: FormatContext{
ptr: C.avformat_alloc_context(),
},
}
if ofc.ptr == nil {
return nil, ErrInvalidOutput
}
cURL := C.CString(url)
defer C.free(unsafe.Pointer(cURL))
var ret C.int
if fmt != nil && fmt.ptr != nil {
ret = C.avformat_alloc_output_context2(&ofc.ptr, fmt.ptr, nil, cURL)
} else {
ret = C.avformat_alloc_output_context2(&ofc.ptr, nil, nil, cURL)
}
if ret < 0 {
C.avformat_free_context(ofc.ptr)
return nil, &FFmpegError{
Code: int(ret),
Message: "failed to allocate output context",
Op: "AllocOutputContext",
}
}
return ofc, nil
}
// AddStream adds a new stream
func (ofc *OutputFormatContext) AddStream(codec *Codec) (*Stream, error) {
if ofc.ptr == nil || codec == nil || codec.ptr == nil {
return nil, ErrInvalidCodec
}
stream := C.avformat_new_stream(ofc.ptr, codec.ptr)
if stream == nil {
return nil, ErrInvalidCodec
}
return StreamFromC(stream), nil
}
// SetOformat sets the output format
func (ofc *OutputFormatContext) SetOformat(fmt *OutputFormat) {
if ofc.ptr != nil && fmt != nil && fmt.ptr != nil {
ofc.ptr.oformat = fmt.ptr
}
}
// DumpFormat dumps format info
func (ofc *OutputFormatContext) DumpFormat(idx int, url string, isOutput bool) {
ofc.FormatContext.DumpFormat(idx, url, isOutput)
}
// WriteHeader writes the header
func (ofc *OutputFormatContext) WriteHeader() error {
if ofc.ptr == nil {
return ErrInvalidOutput
}
if (unsafe.Pointer(ofc.ptr.pb) != nil) && (ofc.ptr.flags&C.AVFMT_NOFILE) == 0 {
// file handle has been created by the caller
} else {
// let avformat do it
}
ret := C.avformat_write_header(ofc.ptr, nil)
if ret < 0 {
return &FFmpegError{
Code: int(ret),
Message: "failed to write header",
Op: "WriteHeader",
}
}
return nil
}
// WriteTrailer writes the trailer
func (ofc *OutputFormatContext) WriteTrailer() error {
if ofc.ptr == nil {
return ErrInvalidOutput
}
ret := C.av_write_trailer(ofc.ptr)
if ret < 0 {
return &FFmpegError{
Code: int(ret),
Message: "failed to write trailer",
Op: "WriteTrailer",
}
}
return nil
}
// OutputFormat represents an output format
type OutputFormat struct {
ptr *C.AVOutputFormat
}
// OutputFormatFromC converts a C pointer to OutputFormat
func OutputFormatFromC(ptr *C.AVOutputFormat) *OutputFormat {
return &OutputFormat{ptr: ptr}
}
// CPtr returns the underlying C pointer
func (of *OutputFormat) CPtr() *C.AVOutputFormat {
return of.ptr
}
// GuessFormat guesses the output format
func GuessFormat(shortName, filename string) *OutputFormat {
cShortName := C.CString(shortName)
cFilename := C.CString(filename)
defer C.free(unsafe.Pointer(cShortName))
defer C.free(unsafe.Pointer(cFilename))
ptr := C.av_guess_format(cShortName, cFilename, nil)
if ptr == nil {
return nil
}
return OutputFormatFromC(ptr)
}

111
pkg/ffmpeg/frame.go Normal file
View File

@ -0,0 +1,111 @@
package ffmpeg
/*
#include <libavutil/frame.h>
*/
import "C"
import "unsafe"
// Frame represents a decoded video/audio frame
type Frame struct {
ptr *C.AVFrame
}
// AllocFrame allocates an empty frame
func AllocFrame() *Frame {
return &Frame{
ptr: C.av_frame_alloc(),
}
}
// FreeFrame frees the frame
func (p *Frame) Free() {
if p.ptr != nil {
C.av_frame_free(&p.ptr)
p.ptr = nil
}
}
// FrameFromC converts a C pointer to Frame
func FrameFromC(ptr *C.AVFrame) *Frame {
return &Frame{ptr: ptr}
}
// CPtr returns the underlying C pointer
func (f *Frame) CPtr() *C.AVFrame {
return f.ptr
}
// Width returns the frame width
func (f *Frame) Width() int {
if f.ptr == nil {
return 0
}
return int(f.ptr.width)
}
// Height returns the frame height
func (f *Frame) Height() int {
if f.ptr == nil {
return 0
}
return int(f.ptr.height)
}
// Format returns the pixel/sample format
func (f *Frame) Format() int {
if f.ptr == nil {
return -1
}
return int(f.ptr.format)
}
// Linesize returns the line size
func (f *Frame) Linesize(i int) int {
if f.ptr == nil || i < 0 || i >= C.AVMEDIA_TYPE_NB {
return 0
}
return int(f.ptr.linesize[i])
}
// Data returns the frame data
func (f *Frame) Data(i int) []byte {
if f.ptr == nil || i < 0 || i >= C.AVMEDIA_TYPE_NB {
return nil
}
size := f.Linesize(i) * f.Height()
if size <= 0 {
return nil
}
return C.GoBytes(unsafe.Pointer(f.ptr.data[i]), C.int(size))
}
// NbSamples returns the number of audio samples
func (f *Frame) NbSamples() int {
if f.ptr == nil {
return 0
}
return int(f.ptr.nb_samples)
}
// PTS returns the presentation timestamp
func (f *Frame) PTS() int64 {
if f.ptr == nil {
return 0
}
return int64(f.ptr.pts)
}
// SetPTS sets the presentation timestamp
func (f *Frame) SetPTS(pts int64) {
if f.ptr != nil {
f.ptr.pts = C.int64_t(pts)
}
}
// Unref unreferences the frame
func (f *Frame) Unref() {
if f.ptr != nil {
C.av_frame_unref(f.ptr)
}
}

118
pkg/ffmpeg/packet.go Normal file
View File

@ -0,0 +1,118 @@
package ffmpeg
/*
#include <libavcodec/avcodec.h>
#include <libavcodec/packet.h>
*/
import "C"
import "unsafe"
// Packet represents an encoded data packet
type Packet struct {
ptr *C.AVPacket
}
// AllocPacket allocates an empty packet
func AllocPacket() *Packet {
return &Packet{
ptr: C.av_packet_alloc(),
}
}
// FreePacket frees the packet
func (p *Packet) Free() {
if p.ptr != nil {
C.av_packet_free(&p.ptr)
p.ptr = nil
}
}
// PacketFromC converts a C pointer to Packet
func PacketFromC(ptr *C.AVPacket) *Packet {
return &Packet{ptr: ptr}
}
// CPtr returns the underlying C pointer
func (p *Packet) CPtr() *C.AVPacket {
return p.ptr
}
// Data returns the packet data
func (p *Packet) Data() []byte {
if p.ptr == nil {
return nil
}
size := int(p.ptr.size)
if size <= 0 || p.ptr.data == nil {
return nil
}
return C.GoBytes(unsafe.Pointer(p.ptr.data), C.int(size))
}
// Size returns the packet size
func (p *Packet) Size() int {
if p.ptr == nil {
return 0
}
return int(p.ptr.size)
}
// PTS returns the presentation timestamp
func (p *Packet) PTS() int64 {
if p.ptr == nil {
return 0
}
return int64(p.ptr.pts)
}
// DTS returns the decoding timestamp
func (p *Packet) DTS() int64 {
if p.ptr == nil {
return 0
}
return int64(p.ptr.dts)
}
// SetPTS sets the presentation timestamp
func (p *Packet) SetPTS(pts int64) {
if p.ptr != nil {
p.ptr.pts = C.int64_t(pts)
}
}
// SetDTS sets the decoding timestamp
func (p *Packet) SetDTS(dts int64) {
if p.ptr != nil {
p.ptr.dts = C.int64_t(dts)
}
}
// StreamIndex returns the stream index
func (p *Packet) StreamIndex() int {
if p.ptr == nil {
return -1
}
return int(p.ptr.stream_index)
}
// SetStreamIndex sets the stream index
func (p *Packet) SetStreamIndex(idx int) {
if p.ptr != nil {
p.ptr.stream_index = C.int(idx)
}
}
// Flags returns the packet flags
func (p *Packet) Flags() int {
if p.ptr == nil {
return 0
}
return int(p.ptr.flags)
}
// Unref unreferences the packet data
func (p *Packet) Unref() {
if p.ptr != nil {
C.av_packet_unref(p.ptr)
}
}