Example #1
0
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
}
Example #2
0
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
}