diff --git a/cmd/ffmpeg-cli/main.go b/cmd/ffmpeg-cli/main.go new file mode 100644 index 0000000..4d3e90f --- /dev/null +++ b/cmd/ffmpeg-cli/main.go @@ -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) +} diff --git a/examples/simple-transcode/main.go b/examples/simple-transcode/main.go new file mode 100644 index 0000000..65ad3fe --- /dev/null +++ b/examples/simple-transcode/main.go @@ -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 ") + 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 +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f311f0e --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.kingecg.top/kingecg/goffmpeg + +//go:build cgo + +go 1.25.1 diff --git a/pkg/ffmpeg/cgo.go b/pkg/ffmpeg/cgo.go new file mode 100644 index 0000000..20f6bc5 --- /dev/null +++ b/pkg/ffmpeg/cgo.go @@ -0,0 +1,12 @@ +package ffmpeg + +/* +#cgo pkg-config: libavcodec libavformat libavutil libavfilter libswscale libswresample +#cgo LDFLAGS: -lavcodec -lavformat -lavutil -lavfilter -lswscale -lswresample + +#include +#include +#include +#include +*/ +import "C" \ No newline at end of file diff --git a/pkg/ffmpeg/codec.go b/pkg/ffmpeg/codec.go new file mode 100644 index 0000000..2ebb5dc --- /dev/null +++ b/pkg/ffmpeg/codec.go @@ -0,0 +1,288 @@ +package ffmpeg + +/* +#include + +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) + } +} diff --git a/pkg/ffmpeg/errors.go b/pkg/ffmpeg/errors.go new file mode 100644 index 0000000..74b8b65 --- /dev/null +++ b/pkg/ffmpeg/errors.go @@ -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:]) +} diff --git a/pkg/ffmpeg/ffmpeg.go b/pkg/ffmpeg/ffmpeg.go new file mode 100644 index 0000000..adb79d1 --- /dev/null +++ b/pkg/ffmpeg/ffmpeg.go @@ -0,0 +1,497 @@ +package ffmpeg + +/* +#include +#include +*/ +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) +} diff --git a/pkg/ffmpeg/frame.go b/pkg/ffmpeg/frame.go new file mode 100644 index 0000000..5dcfc4f --- /dev/null +++ b/pkg/ffmpeg/frame.go @@ -0,0 +1,111 @@ +package ffmpeg + +/* +#include +*/ +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) + } +} diff --git a/pkg/ffmpeg/packet.go b/pkg/ffmpeg/packet.go new file mode 100644 index 0000000..bef9add --- /dev/null +++ b/pkg/ffmpeg/packet.go @@ -0,0 +1,118 @@ +package ffmpeg + +/* +#include +#include +*/ +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) + } +}