mirror of
				https://github.com/livepeer/lpms
				synced 2025-11-01 03:53:04 +08:00 
			
		
		
		
	ffmpeg: Return stats on decoded stream from transcoder.
This commit is contained in:
		| @@ -75,7 +75,8 @@ func main() { | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	for i, r := range res { | ||||
| 	fmt.Printf("profile=input frames=%v pixels=%v\n", res.Decoded.Frames, res.Decoded.Pixels) | ||||
| 	for i, r := range res.Encoded { | ||||
| 		fmt.Printf("profile=%v frames=%v pixels=%v\n", profiles[i].Name, r.Frames, r.Pixels) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -46,6 +46,11 @@ type MediaInfo struct { | ||||
| 	Pixels int64 | ||||
| } | ||||
|  | ||||
| type TranscodeResults struct { | ||||
| 	Decoded MediaInfo | ||||
| 	Encoded []MediaInfo | ||||
| } | ||||
|  | ||||
| func RTMPToHLS(localRTMPUrl string, outM3U8 string, tmpl string, seglen_secs string, seg_start int) error { | ||||
| 	inp := C.CString(localRTMPUrl) | ||||
| 	outp := C.CString(outM3U8) | ||||
| @@ -128,7 +133,7 @@ func Transcode2(input *TranscodeOptionsIn, ps []TranscodeOptions) error { | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func Transcode3(input *TranscodeOptionsIn, ps []TranscodeOptions) ([]MediaInfo, error) { | ||||
| func Transcode3(input *TranscodeOptionsIn, ps []TranscodeOptions) (*TranscodeResults, error) { | ||||
| 	if input == nil { | ||||
| 		return nil, ErrTranscoderInp | ||||
| 	} | ||||
| @@ -182,7 +187,8 @@ func Transcode3(input *TranscodeOptionsIn, ps []TranscodeOptions) ([]MediaInfo, | ||||
| 	} | ||||
| 	inp := &C.input_params{fname: fname, hw_type: hw_type, device: device} | ||||
| 	results := make([]C.output_results, len(ps)) | ||||
| 	ret := int(C.lpms_transcode(inp, (*C.output_params)(¶ms[0]), (*C.output_results)(&results[0]), C.int(len(params)))) | ||||
| 	decoded := &C.output_results{} | ||||
| 	ret := int(C.lpms_transcode(inp, (*C.output_params)(¶ms[0]), (*C.output_results)(&results[0]), C.int(len(params)), decoded)) | ||||
| 	if 0 != ret { | ||||
| 		glog.Infof("Transcoder Return : %v\n", Strerror(ret)) | ||||
| 		return nil, ErrorMap[ret] | ||||
| @@ -194,7 +200,11 @@ func Transcode3(input *TranscodeOptionsIn, ps []TranscodeOptions) ([]MediaInfo, | ||||
| 			Pixels: int64(r.pixels), | ||||
| 		} | ||||
| 	} | ||||
| 	return tr, nil | ||||
| 	dec := MediaInfo{ | ||||
| 		Frames: int(decoded.frames), | ||||
| 		Pixels: int64(decoded.pixels), | ||||
| 	} | ||||
| 	return &TranscodeResults{Encoded: tr, Decoded: dec}, nil | ||||
| } | ||||
|  | ||||
| func InitFFmpeg() { | ||||
|   | ||||
| @@ -377,8 +377,97 @@ func TestTranscoder_Timestamp(t *testing.T) { | ||||
| 	run(cmd) | ||||
| } | ||||
|  | ||||
| func TestTranscoder_Statistics(t *testing.T) { | ||||
| 	// Checks the stats returned after transcoding | ||||
| func TestTranscoderStatistics_Decoded(t *testing.T) { | ||||
| 	// Checks the decoded stats returned after transcoding | ||||
|  | ||||
| 	var ( | ||||
| 		totalPixels int64 | ||||
| 		totalFrames int | ||||
| 	) | ||||
|  | ||||
| 	run, dir := setupTest(t) | ||||
| 	defer os.RemoveAll(dir) | ||||
|  | ||||
| 	// segment using our muxer. This should produce 4 segments. | ||||
| 	err := RTMPToHLS("../transcoder/test.ts", dir+"/test.m3u8", dir+"/test_%d.ts", "1", 0) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
|  | ||||
| 	// Use various resolutions to test input | ||||
| 	// Quickcheck style tests would be nice here one day? | ||||
| 	profiles := []VideoProfile{P144p30fps16x9, P240p30fps16x9, P360p30fps16x9, P576p30fps16x9} | ||||
|  | ||||
| 	// Transcode some data, save encoded statistics, then attempt to re-transcode | ||||
| 	// Ensure decoded re-transcode stats match original transcoded statistics | ||||
| 	for i, p := range profiles { | ||||
| 		oname := fmt.Sprintf("%s/out_%d.ts", dir, i) | ||||
| 		out := []TranscodeOptions{TranscodeOptions{Profile: p, Oname: oname}} | ||||
| 		in := &TranscodeOptionsIn{Fname: fmt.Sprintf("%s/test_%d.ts", dir, i)} | ||||
| 		res, err := Transcode3(in, out) | ||||
| 		if err != nil { | ||||
| 			t.Error(err) | ||||
| 		} | ||||
| 		info := res.Encoded[0] | ||||
|  | ||||
| 		// Now attempt to re-encode the transcoded data | ||||
| 		// and check decoded results from *that* | ||||
| 		p.Framerate = 10 | ||||
| 		in = &TranscodeOptionsIn{Fname: oname} | ||||
| 		out = []TranscodeOptions{TranscodeOptions{Profile: p, Oname: "out.ts"}} | ||||
| 		res, err = Transcode3(in, out) | ||||
| 		if err != nil { | ||||
| 			t.Error(err) | ||||
| 		} | ||||
| 		w, h, err := VideoProfileResolution(p) | ||||
| 		if err != nil { | ||||
| 			t.Error(err) | ||||
| 		} | ||||
|  | ||||
| 		// Check pixel counts | ||||
| 		if info.Pixels != res.Decoded.Pixels { | ||||
| 			t.Error("Mismatched pixel counts") | ||||
| 		} | ||||
| 		if info.Pixels != int64(w*h*res.Decoded.Frames) { | ||||
| 			t.Error("Mismatched pixel counts") | ||||
| 		} | ||||
| 		// Check frame counts | ||||
| 		if info.Frames != res.Decoded.Frames { | ||||
| 			t.Error("Mismatched frame counts") | ||||
| 		} | ||||
| 		if info.Frames != int(res.Decoded.Pixels/int64(w*h)) { | ||||
| 			t.Error("Mismatched frame counts") | ||||
| 		} | ||||
| 		totalPixels += info.Pixels | ||||
| 		totalFrames += info.Frames | ||||
| 	} | ||||
|  | ||||
| 	// Now for something fun. Concatenate our segments of various resolutions | ||||
| 	// Run them through the transcoder, and check the sum of pixels / frames match | ||||
| 	// Ensures we can properly accommodate mid-stream resolution changes. | ||||
| 	cmd := ` | ||||
|         set -eux | ||||
|         cd "$0" | ||||
|         cat out_0.ts out_1.ts out_2.ts out_3.ts > combined.ts | ||||
|     ` | ||||
| 	run(cmd) | ||||
| 	in := &TranscodeOptionsIn{Fname: dir + "/combined.ts"} | ||||
| 	out := []TranscodeOptions{TranscodeOptions{Profile: P240p30fps16x9, Oname: "out.ts"}} | ||||
|  | ||||
| 	res, err := Transcode3(in, out) | ||||
| 	if err != nil { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 	if totalPixels != res.Decoded.Pixels { | ||||
| 		t.Error("Mismatched total pixel counts") | ||||
| 	} | ||||
| 	if totalFrames != res.Decoded.Frames { | ||||
| 		t.Errorf("Mismatched total frame counts - %d vs %d", totalFrames, res.Decoded.Frames) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTranscoder_Statistics_Encoded(t *testing.T) { | ||||
| 	// Checks the encoded stats returned after transcoding | ||||
|  | ||||
| 	run, dir := setupTest(t) | ||||
| 	defer os.RemoveAll(dir) | ||||
| @@ -412,7 +501,7 @@ func TestTranscoder_Statistics(t *testing.T) { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
|  | ||||
| 	for i, r := range res { | ||||
| 	for i, r := range res.Encoded { | ||||
| 		w, h, err := VideoProfileResolution(out[i].Profile) | ||||
| 		if err != nil { | ||||
| 			t.Error(err) | ||||
| @@ -481,10 +570,10 @@ func TestTranscoder_StatisticsAspectRatio(t *testing.T) { | ||||
| 	pAdj := VideoProfile{Resolution: "124x456", Framerate: 15, Bitrate: "100k"} | ||||
| 	out := []TranscodeOptions{TranscodeOptions{Profile: pAdj, Oname: dir + "/adj.mp4"}} | ||||
| 	res, err := Transcode3(&TranscodeOptionsIn{Fname: dir + "/test.ts"}, out) | ||||
| 	if err != nil || len(res) <= 0 { | ||||
| 	if err != nil || len(res.Encoded) <= 0 { | ||||
| 		t.Error(err) | ||||
| 	} | ||||
| 	r := res[0] | ||||
| 	r := res.Encoded[0] | ||||
| 	if r.Frames != int(pAdj.Framerate) || r.Pixels != int64(r.Frames*124*70) { | ||||
| 		t.Error(fmt.Errorf("Results did not match: %v ", r)) | ||||
| 	} | ||||
|   | ||||
| @@ -759,7 +759,7 @@ proc_cleanup: | ||||
| #define MAX_OUTPUT_SIZE 10 | ||||
|  | ||||
| int lpms_transcode(input_params *inp, output_params *params, | ||||
|     output_results *results, int nb_outputs) | ||||
|     output_results *results, int nb_outputs, output_results *decoded_results) | ||||
| { | ||||
| #define main_err(msg) { \ | ||||
|   if (!ret) ret = AVERROR(EINVAL); \ | ||||
| @@ -821,6 +821,11 @@ int lpms_transcode(input_params *inp, output_params *params, | ||||
|     else if (ret < 0) main_err("transcoder: Could not decode; stopping\n"); | ||||
|     ist = ictx.ic->streams[ipkt.stream_index]; | ||||
|  | ||||
|     if (AVMEDIA_TYPE_VIDEO == ist->codecpar->codec_type) { | ||||
|       decoded_results->frames++; | ||||
|       decoded_results->pixels += dframe->width * dframe->height; | ||||
|     } | ||||
|  | ||||
|     for (i = 0; i < nb_outputs; i++) { | ||||
|       struct output_ctx *octx = &outputs[i]; | ||||
|       struct filter_ctx *filter = NULL; | ||||
|   | ||||
| @@ -31,6 +31,6 @@ typedef struct { | ||||
|  | ||||
| void lpms_init(); | ||||
| int  lpms_rtmp2hls(char *listen, char *outf, char *ts_tmpl, char *seg_time, char *seg_start); | ||||
| int  lpms_transcode(input_params *inp, output_params *params, output_results *results, int nb_outputs); | ||||
| int  lpms_transcode(input_params *inp, output_params *params, output_results *results, int nb_outputs, output_results *decoded_results); | ||||
|  | ||||
| #endif // _LPMS_FFMPEG_H_ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Josh Allmann
					Josh Allmann