Skip to content

Instantly share code, notes, and snippets.

@notedit
Created February 8, 2020 16:19
Show Gist options
  • Save notedit/cb70e2f442465dc42716cba3590303e9 to your computer and use it in GitHub Desktop.
Save notedit/cb70e2f442465dc42716cba3590303e9 to your computer and use it in GitHub Desktop.
transcode.go
package main
import (
"flag"
"fmt"
"io"
"log"
"strings"
"github.com/3d0c/gmf"
)
type arrayFlags []string
func (i *arrayFlags) String() string {
return strings.Join(*i, " ")
}
func (i *arrayFlags) Set(value string) error {
*i = append(*i, strings.TrimSpace(value))
return nil
}
func addStream(name string, oc *gmf.FmtCtx, ist *gmf.Stream) (int, int, error) {
var (
cc *gmf.CodecCtx
ost *gmf.Stream
)
codec, err := gmf.FindEncoder(name)
if err != nil {
return 0, 0, err
}
if cc = gmf.NewCodecCtx(codec); cc == nil {
return 0, 0, fmt.Errorf("unable to create codec context")
}
if oc.IsGlobalHeader() {
cc.SetFlag(gmf.CODEC_FLAG_GLOBAL_HEADER)
}
if codec.IsExperimental() {
cc.SetStrictCompliance(gmf.FF_COMPLIANCE_EXPERIMENTAL)
}
if cc.Type() == gmf.AVMEDIA_TYPE_AUDIO {
cc.SetSampleFmt(ist.CodecCtx().SampleFmt())
cc.SetSampleRate(ist.CodecCtx().SampleRate())
cc.SetChannels(ist.CodecCtx().Channels())
channelLayout := cc.SelectChannelLayout()
cc.SetChannelLayout(channelLayout)
cc.SelectSampleRate()
cc.SetTimeBase(gmf.AVR{Num: 1, Den: ist.CodecCtx().SampleRate()})
}
if cc.Type() == gmf.AVMEDIA_TYPE_VIDEO {
cc.SetTimeBase(gmf.AVR{Num: 1, Den: 25})
cc.SetProfile(gmf.FF_PROFILE_MPEG4_SIMPLE)
cc.SetDimension(ist.CodecCtx().Width(), ist.CodecCtx().Height())
cc.SetPixFmt(ist.CodecCtx().PixFmt())
}
if err := cc.Open(nil); err != nil {
return 0, 0, err
}
par := gmf.NewCodecParameters()
if err = par.FromContext(cc); err != nil {
return 0, 0, fmt.Errorf("error creating codec parameters from context - %s", err)
}
defer par.Free()
if ost = oc.NewStream(codec); ost == nil {
return 0, 0, fmt.Errorf("unable to create new stream in output context")
}
ost.CopyCodecPar(par)
ost.SetCodecCtx(cc)
if cc.Type() == gmf.AVMEDIA_TYPE_VIDEO {
ost.SetTimeBase(cc.TimeBase().AVR())
//ost.SetRFrameRate(gmf.AVR{Num: 25, Den: 1})
}
return ist.Index(), ost.Index(), nil
}
func main() {
var (
src arrayFlags
dst string
streamMap map[int]int = make(map[int]int)
pts int64 = 0
)
flag.Var(&src, "src", "source files, e.g.: -src=1.mp4 -src=2.mp4")
flag.StringVar(&dst, "dst", "", "destination file, e.g. -dst=result.flv")
flag.Parse()
if len(src) == 0 || dst == "" {
log.Fatal("at least one source and destination required")
}
octx, err := gmf.NewOutputCtx(dst)
if err != nil {
log.Fatal(err)
}
defer octx.Free()
for _, name := range src {
log.Printf("Processing file %s\n", name)
ictx, err := gmf.NewInputCtx(name)
if err != nil {
log.Fatal(err)
}
if len(streamMap) == 0 {
srcVideoStream, err := ictx.GetBestStream(gmf.AVMEDIA_TYPE_VIDEO)
if err != nil {
log.Fatalf("No video stream found in %s", name)
} else {
i, o, err := addStream("libx264", octx, srcVideoStream)
if err != nil {
log.Fatal(err)
}
streamMap[i] = o
}
srcAudioStream, err := ictx.GetBestStream(gmf.AVMEDIA_TYPE_AUDIO)
if err != nil {
log.Println("No audio stream found in", name)
} else {
i, o, err := addStream("aac", octx, srcAudioStream)
if err != nil {
log.Fatal(err)
}
streamMap[i] = o
}
if err := octx.WriteHeader(); err != nil {
log.Fatalf("error writing header - %s\n", err)
}
}
var (
pkt *gmf.Packet
ist, ost *gmf.Stream
streamIdx int
flush int = -1
)
for {
if flush < 0 {
pkt, err = ictx.GetNextPacket()
if err != nil && err != io.EOF {
if pkt != nil {
pkt.Free()
}
log.Fatalf("error getting next packet - %s", err)
} else if err != nil && pkt == nil {
log.Printf("=== flushing \n")
flush++
break
}
}
if flush == len(streamMap) {
break
}
if flush < 0 {
streamIdx = pkt.StreamIndex()
} else {
streamIdx = flush
flush++
}
if _, ok := streamMap[streamIdx]; !ok {
if pkt != nil {
pkt.Free()
}
continue
}
ist, err = ictx.GetStream(streamIdx)
if err != nil {
if pkt != nil {
pkt.Free()
}
log.Fatalf("error getting stream %d - %s", pkt.StreamIndex(), err)
}
ost, err = octx.GetStream(streamMap[ist.Index()])
if err != nil {
if pkt != nil {
pkt.Free()
}
log.Fatalf("error getting stream %d - %s", pkt.StreamIndex(), err)
}
frames, err := ist.CodecCtx().Decode(pkt)
if err != nil {
log.Printf("error decoding - %s\n", err)
continue
}
for _, frame := range frames {
frame.SetPts(pts)
pts++
}
packets, err := ost.CodecCtx().Encode(frames, flush)
if err != nil {
log.Println(err)
continue
}
for _, op := range packets {
gmf.RescaleTs(op, ist.TimeBase(), ost.TimeBase())
op.SetStreamIndex(ost.Index())
if err = octx.WritePacket(op); err != nil {
break
}
op.Free()
}
for _, frame := range frames {
if frame != nil {
frame.Free()
}
}
if pkt != nil {
pkt.Free()
}
}
for i := 0; i < ictx.StreamsCnt(); i++ {
st, _ := ictx.GetStream(i)
st.CodecCtx().Free()
st.Free()
}
ictx.Free()
}
octx.WriteTrailer()
for i := 0; i < octx.StreamsCnt(); i++ {
st, _ := octx.GetStream(i)
st.CodecCtx().Free()
st.Free()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment