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 ""
|
||||||
echo "Build complete!"
|
echo "Build complete!"
|
||||||
echo "Run with: ./bin/simple-transcode <input> <output>"
|
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"
|
"git.kingecg.top/kingecg/goffmpeg/pkg/ffmpeg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Simple transcoding example using goffmpeg library
|
// Simple remuxing example using goffmpeg library
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 3 {
|
if len(os.Args) < 3 {
|
||||||
fmt.Println("Usage: simple-transcode <input> <output>")
|
fmt.Println("Usage: simple-transcode <input> <output>")
|
||||||
|
|
@ -26,50 +26,149 @@ func main() {
|
||||||
if err := ic.OpenInput(inputURL); err != nil {
|
if err := ic.OpenInput(inputURL); err != nil {
|
||||||
log.Fatalf("Failed to open input %s: %v", inputURL, err)
|
log.Fatalf("Failed to open input %s: %v", inputURL, err)
|
||||||
}
|
}
|
||||||
defer ic.Close()
|
|
||||||
|
|
||||||
// Find stream info
|
// Find stream info
|
||||||
if err := ic.FindStreamInfo(); err != nil {
|
if err := ic.FindStreamInfo(); err != nil {
|
||||||
log.Fatalf("Failed to find stream info: %v", err)
|
log.Fatalf("Failed to find stream info: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump input format info
|
|
||||||
fmt.Printf("Input: %s\n", inputURL)
|
fmt.Printf("Input: %s\n", inputURL)
|
||||||
ic.DumpFormat(0, inputURL, false)
|
ic.DumpFormat(0, inputURL, false)
|
||||||
|
|
||||||
// Find video stream
|
// Guess output format
|
||||||
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)
|
of := ffmpeg.GuessFormat("", outputURL)
|
||||||
if of == nil {
|
if of == nil {
|
||||||
log.Fatalf("Failed to guess output format")
|
log.Fatalf("Failed to guess output format")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create output context
|
||||||
ofc, err := ffmpeg.AllocOutputContext(outputURL, of)
|
ofc, err := ffmpeg.AllocOutputContext(outputURL, of)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to allocate output context: %v", err)
|
log.Fatalf("Failed to allocate output context: %v", err)
|
||||||
}
|
}
|
||||||
defer ofc.Free()
|
defer ofc.Free()
|
||||||
|
|
||||||
// Copy stream from input to output
|
// Get input streams
|
||||||
_, err = ofc.AddStream(nil)
|
inputStreams := ic.Streams()
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to add stream: %v", err)
|
// 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.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
|
// Open opens the codec
|
||||||
func (c *Context) Open(codec *Codec) error {
|
func (c *Context) Open(codec *Codec) error {
|
||||||
if c.ptr == nil || codec == nil || codec.ptr == nil {
|
if c.ptr == nil || codec == nil || codec.ptr == nil {
|
||||||
|
|
@ -212,6 +253,23 @@ func (c *Context) Open(codec *Codec) error {
|
||||||
return nil
|
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
|
// SendPacket sends a packet to the decoder
|
||||||
func (c *Context) SendPacket(pkt *Packet) error {
|
func (c *Context) SendPacket(pkt *Packet) error {
|
||||||
if c.ptr == nil {
|
if c.ptr == nil {
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,33 @@ func (s *Stream) SetCodecParameters(cp *CodecParameters) error {
|
||||||
return nil
|
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)
|
// Codec returns the codec context (deprecated: use CodecParameters instead)
|
||||||
func (s *Stream) Codec() *Context {
|
func (s *Stream) Codec() *Context {
|
||||||
// In FFmpeg 4.0+, codec field was removed from AVStream
|
// 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
|
return ofc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddStream adds a new stream
|
// AddStream adds a new stream with optional codec
|
||||||
func (ofc *OutputFormatContext) AddStream(codec *Codec) (*Stream, error) {
|
func (ofc *OutputFormatContext) AddStream(codec *Codec) (*Stream, error) {
|
||||||
if ofc.ptr == nil || codec == nil || codec.ptr == nil {
|
if ofc.ptr == nil {
|
||||||
return nil, ErrInvalidCodec
|
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 {
|
if stream == nil {
|
||||||
return nil, ErrInvalidCodec
|
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
|
// DumpFormat dumps format info
|
||||||
func (ofc *OutputFormatContext) DumpFormat(idx int, url string, isOutput bool) {
|
func (ofc *OutputFormatContext) DumpFormat(idx int, url string, isOutput bool) {
|
||||||
ofc.FormatContext.DumpFormat(idx, url, isOutput)
|
ofc.FormatContext.DumpFormat(idx, url, isOutput)
|
||||||
|
|
@ -433,12 +492,6 @@ func (ofc *OutputFormatContext) WriteHeader() error {
|
||||||
return ErrInvalidOutput
|
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)
|
ret := C.avformat_write_header(ofc.ptr, nil)
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
return &FFmpegError{
|
return &FFmpegError{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue