Compare commits
2 Commits
3b0dbf6014
...
3fdc475316
| Author | SHA1 | Date |
|---|---|---|
|
|
3fdc475316 | |
|
|
8a258d8b60 |
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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>"
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
module git.kingecg.top/kingecg/goffmpeg
|
||||
|
||||
//go:build cgo
|
||||
|
||||
go 1.25.1
|
||||
|
|
@ -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"
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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:])
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue