func writeMetaKeyframes(frReader *flv.FlvReader, frWriter *flv.FlvWriter) (inStart int64) { inStart, metaMap := createMetaKeyframes(frReader) newBuf := new(bytes.Buffer) newEnc := amf0.NewEncoder(newBuf) err := newEnc.Encode(amf0.StringType("onMetaData")) if err != nil { log.Fatalf("%s", err) } err = newEnc.Encode(metaMap) if err != nil { log.Fatalf("%s", err) } cFrame := &flv.CFrame{ Stream: 0, Dts: 0, Type: flv.TAG_TYPE_META, Flavor: flv.METADATA, Body: newBuf.Bytes(), } newMdFrame := flv.MetaFrame{ CFrame: cFrame, } frWriter.WriteFrame(newMdFrame) return inStart }
func createMetaKeyframes(frReader *flv.FlvReader) (inStart int64, metaMapP *amf0.EcmaArrayType) { fi, err := frReader.InFile.Stat() if err != nil { log.Fatal(err) } filesize := fi.Size() frameSize := map[flv.TagType]uint64{flv.TAG_TYPE_VIDEO: 0, flv.TAG_TYPE_AUDIO: 0, flv.TAG_TYPE_META: 0} size := map[flv.TagType]uint64{flv.TAG_TYPE_VIDEO: 0, flv.TAG_TYPE_AUDIO: 0, flv.TAG_TYPE_META: 0} has := map[flv.TagType]bool{flv.TAG_TYPE_VIDEO: false, flv.TAG_TYPE_AUDIO: false, flv.TAG_TYPE_META: false} var lastKeyFrameTs, lastVTs, lastTs uint32 var width, height uint16 var audioRate uint32 var dataFrameSize uint64 = 0 var videoFrames, audioFrames uint32 = 0, 0 var stereo bool = false var videoCodec, audioCodec uint8 = 0, 0 var audioSampleSize uint32 = 0 var hasKeyframes bool = false var oldOnMetaDataSize int64 = 0 var kfs []kfTimePos nextFrame: for { frame, rerr := frReader.ReadFrame() switch { case rerr != nil && !readRecover: log.Fatal(rerr) case rerr != nil && rerr.IsRecoverable(): _, err, skipBytes := frReader.Recover(rerr, maxScanSize) if err != nil { log.Fatalf("recovery error: %s", err) } log.Printf("recover: got fine frame after %d bytes", skipBytes) continue nextFrame } if frame != nil { switch tfr := frame.(type) { // TODO: AvcFrame support case flv.VideoFrame: if (width == 0) || (height == 0) { width, height = tfr.Width, tfr.Height //log.Printf("VideoCodec: %d, Width: %d, Height: %d", tfr.CodecId, tfr.Width, tfr.Height) } switch tfr.Flavor { case flv.KEYFRAME: lastKeyFrameTs = tfr.Dts hasKeyframes = true kfs = append(kfs, kfTimePos{Dts: tfr.Dts, Position: tfr.Position}) default: videoFrames++ } lastVTs = tfr.Dts videoCodec = uint8(tfr.CodecId) case flv.AudioFrame: audioRate = tfr.Rate if tfr.Channels == flv.AUDIO_TYPE_STEREO { stereo = true } switch tfr.BitSize { case flv.AUDIO_SIZE_8BIT: audioSampleSize = 8 case flv.AUDIO_SIZE_16BIT: audioSampleSize = 16 } audioCodec = uint8(tfr.CodecId) audioFrames++ case flv.MetaFrame: buf := bytes.NewReader(tfr.Body) dec := amf0.NewDecoder(buf) evName, err := dec.Decode() if err != nil { log.Printf("Err %v at DTS %d", err, tfr.Dts) break nextFrame } switch evName { case amf0.StringType("onMetaData"): oldOnMetaDataSize = int64(tfr.PrevTagSize) md, err := dec.Decode() if err != nil { break nextFrame } var ea map[amf0.StringType]interface{} switch md := md.(type) { case *amf0.EcmaArrayType: ea = *md case *amf0.ObjectType: ea = *md } if verbose { log.Printf("Old onMetaData") for k, v := range ea { log.Printf("%v = %v\n", k, v) } } if width == 0 { if v, ok := ((ea)["width"]); ok { width = uint16(v.(amf0.NumberType)) } } if height == 0 { if v, ok := ((ea)["height"]); ok { height = uint16(v.(amf0.NumberType)) } } default: log.Printf("Unknown event: %s\n", evName) } } frameSize[frame.GetType()] += uint64(frame.GetPrevTagSize()) size[frame.GetType()] += uint64(len(*frame.GetBody())) has[frame.GetType()] = true lastTs = frame.GetDts() frameDump(frame) } else { break } } if flvDump && !printInfo { return 0, nil } //log.Printf("KFS: %v", kfs) lastKeyFrameTsF := float32(lastKeyFrameTs) / 1000 lastVTsF := float32(lastVTs) / 1000 duration := float32(lastTs) / 1000 dataFrameSize = frameSize[flv.TAG_TYPE_VIDEO] + frameSize[flv.TAG_TYPE_AUDIO] + frameSize[flv.TAG_TYPE_META] now := time.Now() metadatadate := float64(now.Unix()*1000) + (float64(now.Nanosecond()) / 1000000) videoDataRate := (float32(size[flv.TAG_TYPE_VIDEO]) / float32(duration)) * 8 / 1000 audioDataRate := (float32(size[flv.TAG_TYPE_AUDIO]) / float32(duration)) * 8 / 1000 frameRate := uint8(math.Floor(float64(videoFrames) / float64(duration))) //log.Printf("oldOnMetaDataSize: %d, FileSize: %d, LastKeyFrameTS: %f, LastTS: %f, Width: %d, Height: %d, VideoSize: %d, AudioSize: %d, MetaDataSize: %d, DataSize: %d, Duration: %f, MetadataDate: %f, VideoDataRate: %f, AudioDataRate: %f, FrameRate: %d, AudioRate: %d", oldOnMetaDataSize, filesize, lastKeyFrameTsF, lastVTsF, width, height, videoFrameSize, audioFrameSize, metadataFrameSize, dataFrameSize, duration, metadatadate, videoDataRate, audioDataRate, frameRate, audioRate) kfTimes := make(amf0.StrictArrayType, 0) kfPositions := make(amf0.StrictArrayType, 0) for i := range kfs { kfTimes = append(kfTimes, amf0.NumberType((float64(kfs[i].Dts) / 1000))) kfPositions = append(kfTimes, amf0.NumberType(kfs[i].Position)) } keyFrames := amf0.ObjectType{ "times": &kfTimes, "filepositions": &kfPositions, } has[flv.TAG_TYPE_META] = true metaMap := amf0.EcmaArrayType{ "metadatacreator": amf0.StringType("FlvSAK https://github.com/metachord/flvsak"), "metadatadate": amf0.DateType{TimeZone: 0, Date: metadatadate}, "keyframes": &keyFrames, "hasVideo": amf0.BooleanType(has[flv.TAG_TYPE_VIDEO]), "hasAudio": amf0.BooleanType(has[flv.TAG_TYPE_AUDIO]), "hasMetadata": amf0.BooleanType(has[flv.TAG_TYPE_META]), "hasKeyframes": amf0.BooleanType(hasKeyframes), "hasCuePoints": amf0.BooleanType(false), "videocodecid": amf0.NumberType(videoCodec), "width": amf0.NumberType(width), "height": amf0.NumberType(height), "videosize": amf0.NumberType(frameSize[flv.TAG_TYPE_VIDEO]), "framerate": amf0.NumberType(frameRate), "videodatarate": amf0.NumberType(videoDataRate), "audiocodecid": amf0.NumberType(audioCodec), "stereo": amf0.BooleanType(stereo), "audiosamplesize": amf0.NumberType(audioSampleSize), "audiodelay": amf0.NumberType(0), "audiodatarate": amf0.NumberType(audioDataRate), "audiosize": amf0.NumberType(frameSize[flv.TAG_TYPE_AUDIO]), "audiosamplerate": amf0.NumberType(audioRate), "filesize": amf0.NumberType(filesize), "datasize": amf0.NumberType(dataFrameSize), "lasttimestamp": amf0.NumberType(lastVTsF), "lastkeyframetimestamp": amf0.NumberType(lastKeyFrameTsF), "cuePoints": &amf0.StrictArrayType{}, "duration": amf0.NumberType(duration), "canSeekToEnd": amf0.BooleanType(false), } if verbose { log.Printf("New onMetaData") for k, v := range metaMap { log.Printf("%v = %v\n", k, v) } } buf := new(bytes.Buffer) enc := amf0.NewEncoder(buf) err = enc.Encode(&metaMap) if err != nil { log.Fatalf("%s", err) } newOnMetaDataSize := int64(buf.Len()) + int64(flv.TAG_HEADER_LENGTH) + int64(flv.PREV_TAG_SIZE_LENGTH) //log.Printf("newOnMetaDataSize: %v", newOnMetaDataSize) //log.Printf("oldKeyFrames: %v", &keyFrames) newKfPositions := make(amf0.StrictArrayType, 0) var dataDiff int64 = newOnMetaDataSize - oldOnMetaDataSize for i := range kfs { newKfPositions = append(newKfPositions, amf0.NumberType(uint64(kfs[i].Position+dataDiff))) } keyFrames["filepositions"] = &newKfPositions metaMap["filesize"] = amf0.NumberType(int64(metaMap["filesize"].(amf0.NumberType)) + dataDiff) metaMap["datasize"] = amf0.NumberType(int64(metaMap["datasize"].(amf0.NumberType)) + dataDiff) //log.Printf("newKeyFrames: %v", &keyFrames) inStart = kfs[0].Position return inStart, &metaMap }