Add .gitignore, enhance build script output, and implement codec context methods
This commit is contained in:
parent
3fdc475316
commit
387ecc992a
|
|
@ -0,0 +1 @@
|
|||
examples/bin/
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
Loading…
Reference in New Issue