goffmpeg/pkg/ffmpeg/ffmpeg.go

551 lines
12 KiB
Go

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
}
// SetCodecContextParameters copies parameters from a codec context to this stream
func (s *Stream) SetCodecContextParameters(cc *Context) error {
if s.ptr == nil || cc == nil || cc.ptr == nil {
return ErrInvalidCodec
}
// Manually copy fields from codec context to codec parameters
s.ptr.codecpar.codec_type = cc.ptr.codec_type
s.ptr.codecpar.codec_id = cc.ptr.codec_id
s.ptr.codecpar.bit_rate = cc.ptr.bit_rate
s.ptr.codecpar.width = cc.ptr.width
s.ptr.codecpar.height = cc.ptr.height
// Copy format based on codec type
if cc.ptr.codec_type == C.AVMEDIA_TYPE_VIDEO {
s.ptr.codecpar.format = C.int(cc.ptr.pix_fmt)
} else if cc.ptr.codec_type == C.AVMEDIA_TYPE_AUDIO {
s.ptr.codecpar.format = C.int(cc.ptr.sample_fmt)
}
s.ptr.codecpar.sample_rate = cc.ptr.sample_rate
s.ptr.codecpar.channels = cc.ptr.channels
s.ptr.codecpar.channel_layout = cc.ptr.channel_layout
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 with optional codec
func (ofc *OutputFormatContext) AddStream(codec *Codec) (*Stream, error) {
if ofc.ptr == nil {
return nil, ErrInvalidOutput
}
var stream *C.AVStream
if codec != nil && codec.ptr != nil {
stream = C.avformat_new_stream(ofc.ptr, codec.ptr)
} else {
stream = C.avformat_new_stream(ofc.ptr, nil)
}
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
}
}
// OpenOutput opens the output file
func (ofc *OutputFormatContext) OpenOutput(url string) error {
if ofc.ptr == nil {
return ErrInvalidOutput
}
cURL := C.CString(url)
defer C.free(unsafe.Pointer(cURL))
ret := C.avio_open(&ofc.ptr.pb, cURL, C.AVIO_FLAG_WRITE)
if ret < 0 {
return &FFmpegError{
Code: int(ret),
Message: "failed to open output",
Op: "OpenOutput",
}
}
return nil
}
// CloseOutput closes the output file
func (ofc *OutputFormatContext) CloseOutput() {
if ofc.ptr != nil && ofc.ptr.pb != nil {
C.avio_close(ofc.ptr.pb)
}
}
// 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
}
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)
}