Created
February 8, 2020 16:19
-
-
Save notedit/cb70e2f442465dc42716cba3590303e9 to your computer and use it in GitHub Desktop.
transcode.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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