Add .gitignore, enhance build script output, and implement codec context methods

This commit is contained in:
kingecg 2026-03-20 21:48:53 +08:00
parent 3fdc475316
commit 387ecc992a
6 changed files with 245 additions and 33 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
examples/bin/

View File

@ -12,3 +12,4 @@ go build -o bin/simple-transcode ./simple-transcode
echo ""
echo "Build complete!"
echo "Run with: ./bin/simple-transcode <input> <output>"
echo "Example: ./bin/simple-transcode test.mp4 output.mp4"

View File

@ -8,7 +8,7 @@ import (
"git.kingecg.top/kingecg/goffmpeg/pkg/ffmpeg"
)
// Simple transcoding example using goffmpeg library
// Simple remuxing example using goffmpeg library
func main() {
if len(os.Args) < 3 {
fmt.Println("Usage: simple-transcode <input> <output>")
@ -26,50 +26,149 @@ func main() {
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
// Guess output format
of := ffmpeg.GuessFormat("", outputURL)
if of == nil {
log.Fatalf("Failed to guess output format")
}
// Create output context
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)
// Get input streams
inputStreams := ic.Streams()
// Process streams - select first video and audio only
var videoIdx, audioIdx int = -1, -1
streamCount := 0
for i, is := range inputStreams {
cp := is.CodecParameters()
if cp == nil {
continue
}
streamType := cp.CodecType()
// Skip non-video/audio
if streamType != ffmpeg.CodecTypeVideo && streamType != ffmpeg.CodecTypeAudio {
continue
}
// Skip duplicate audio
if streamType == ffmpeg.CodecTypeAudio && audioIdx >= 0 {
fmt.Printf("\nStream %d: audio (skipped - duplicate)\n", i)
continue
}
// Add stream with same codec (stream copy mode)
os, err := ofc.AddStream(nil)
if err != nil {
fmt.Printf("\nStream %d: failed to add stream: %v\n", i, err)
continue
}
// Copy codec parameters from input to output
if err := os.SetCodecParameters(cp); err != nil {
fmt.Printf("\nStream %d: failed to set codec parameters: %v\n", i, err)
continue
}
// Copy time base
tb := is.TimeBase()
os.SetTimeBase(tb)
if streamType == ffmpeg.CodecTypeVideo {
videoIdx = streamCount
fmt.Printf("\nStream %d: video (stream copy)\n", i)
} else {
audioIdx = streamCount
fmt.Printf("\nStream %d: audio (stream copy)\n", i)
}
streamCount++
}
if videoIdx < 0 && audioIdx < 0 {
log.Fatal("No supported streams found")
}
fmt.Printf("\nOutput: %s\n", outputURL)
fmt.Println("Transcoding setup complete. Use the library APIs to process frames.")
fmt.Printf("Output has %d streams\n", streamCount)
ofc.DumpFormat(0, outputURL, true)
_ = vs // vs is used for reference
// Open output file
if err := ofc.OpenOutput(outputURL); err != nil {
log.Fatalf("Failed to open output: %v", err)
}
// Write header
if err := ofc.WriteHeader(); err != nil {
log.Fatalf("Failed to write header: %v", err)
}
fmt.Println("\nRemuxing...")
pkt := ffmpeg.AllocPacket()
defer pkt.Free()
packetCount := 0
for {
err := ic.ReadPacket(pkt)
if err != nil {
break
}
pktStreamIdx := pkt.StreamIndex()
// Map input stream index to output stream index
var outStreamIdx int
if videoIdx >= 0 && audioIdx >= 0 {
// Both video and audio present
if pktStreamIdx == 0 {
outStreamIdx = videoIdx
} else if pktStreamIdx == 1 {
outStreamIdx = audioIdx
} else {
pkt.Unref()
continue
}
} else if videoIdx >= 0 {
outStreamIdx = videoIdx
} else {
outStreamIdx = audioIdx
}
pkt.SetStreamIndex(outStreamIdx)
if err := ofc.WritePacket(pkt); err != nil {
log.Printf("Warning: failed to write packet: %v", err)
}
pkt.Unref()
packetCount++
if packetCount%100 == 0 {
fmt.Printf("Processed %d packets...\n", packetCount)
}
}
// Write trailer
if err := ofc.WriteTrailer(); err != nil {
log.Fatalf("Failed to write trailer: %v", err)
}
fmt.Printf("\nRemuxing complete! Processed %d packets.\n", packetCount)
fmt.Printf("Output saved to: %s\n", outputURL)
}

Binary file not shown.

View File

@ -195,6 +195,47 @@ func (c *Context) SetBitRate(br int64) {
}
}
// TimeBase returns the time base
func (c *Context) TimeBase() Rational {
if c.ptr == nil {
return Rational{}
}
return Rational{
num: int(c.ptr.time_base.num),
den: int(c.ptr.time_base.den),
}
}
// SetTimeBase sets the time base
func (c *Context) SetTimeBase(r Rational) {
if c.ptr != nil {
c.ptr.time_base.num = C.int(r.num)
c.ptr.time_base.den = C.int(r.den)
}
}
// SetChannelLayout sets the channel layout
func (c *Context) SetChannelLayout(layout uint64) {
if c.ptr != nil {
c.ptr.channel_layout = C.uint64_t(layout)
}
}
// SetSampleRate sets the sample rate
func (c *Context) SetSampleRate(rate int) {
if c.ptr != nil {
c.ptr.sample_rate = C.int(rate)
}
}
// Channels returns the number of channels
func (c *Context) Channels() int {
if c.ptr == nil {
return 0
}
return int(c.ptr.channels)
}
// Open opens the codec
func (c *Context) Open(codec *Codec) error {
if c.ptr == nil || codec == nil || codec.ptr == nil {
@ -212,6 +253,23 @@ func (c *Context) Open(codec *Codec) error {
return nil
}
// CopyParameters copies parameters from CodecParameters to this context
func (c *Context) CopyParameters(cp *CodecParameters) error {
if c.ptr == nil || cp == nil || cp.ptr == nil {
return ErrInvalidCodec
}
ret := C.avcodec_parameters_to_context(c.ptr, cp.ptr)
if ret < 0 {
return &FFmpegError{
Code: int(ret),
Message: "failed to copy parameters",
Op: "CopyParameters",
}
}
return nil
}
// SendPacket sends a packet to the decoder
func (c *Context) SendPacket(pkt *Packet) error {
if c.ptr == nil {

View File

@ -238,6 +238,33 @@ func (s *Stream) SetCodecParameters(cp *CodecParameters) error {
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
@ -401,13 +428,18 @@ func AllocOutputContext(url string, fmt *OutputFormat) (*OutputFormatContext, er
return ofc, nil
}
// AddStream adds a new stream
// AddStream adds a new stream with optional codec
func (ofc *OutputFormatContext) AddStream(codec *Codec) (*Stream, error) {
if ofc.ptr == nil || codec == nil || codec.ptr == nil {
return nil, ErrInvalidCodec
if ofc.ptr == nil {
return nil, ErrInvalidOutput
}
stream := C.avformat_new_stream(ofc.ptr, codec.ptr)
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
}
@ -422,6 +454,33 @@ func (ofc *OutputFormatContext) SetOformat(fmt *OutputFormat) {
}
}
// 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)
@ -433,12 +492,6 @@ func (ofc *OutputFormatContext) WriteHeader() error {
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{