From d38bab6a3f65a7faa914372f379ab6c75039b386 Mon Sep 17 00:00:00 2001 From: AlexKordic Date: Wed, 2 Nov 2022 15:26:36 +0100 Subject: [PATCH] add struct for transcoded media info. use it for calculating bitrate of transcoded results (#135) --- transcode/manifest.go | 7 ++++--- transcode/manifest_test.go | 23 +++++++++++++---------- transcode/transcode.go | 36 +++++++++++++++++++++++++++++++++--- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/transcode/manifest.go b/transcode/manifest.go index d1f6d14d0..6d85d97fa 100644 --- a/transcode/manifest.go +++ b/transcode/manifest.go @@ -72,12 +72,13 @@ func GetSourceSegmentURLs(sourceManifestURL string, manifest m3u8.MediaPlaylist) // Generate a Master manifest, plus one Rendition manifest for each Profile we're transcoding, then write them to storage // Returns the master manifest URL on success -func GenerateAndUploadManifests(sourceManifest m3u8.MediaPlaylist, targetOSURL string, transcodeProfiles []clients.EncodedProfile) (string, error) { +func GenerateAndUploadManifests(sourceManifest m3u8.MediaPlaylist, targetOSURL string, transcodedStats []RenditionStats) (string, error) { // Generate the master + rendition output manifests masterPlaylist := m3u8.NewMasterPlaylist() - for i, profile := range transcodeProfiles { + for i, profile := range transcodedStats { // For each profile, add a new entry to the master manifest + bitsPerSecond := uint32(float64(profile.Bytes) * 8000.0 / float64(profile.DurationMs)) masterPlaylist.Append( fmt.Sprintf("rendition-%d/rendition.m3u8", i), &m3u8.MediaPlaylist{ @@ -85,7 +86,7 @@ func GenerateAndUploadManifests(sourceManifest m3u8.MediaPlaylist, targetOSURL s }, m3u8.VariantParams{ Name: fmt.Sprintf("%d-%s", i, profile.Name), - Bandwidth: uint32(1), // TODO: Don't hardcode - this should come from the transcoder output + Bandwidth: bitsPerSecond, FrameRate: float64(profile.FPS), Resolution: fmt.Sprintf("%dx%d", profile.Width, profile.Height), }, diff --git a/transcode/manifest_test.go b/transcode/manifest_test.go index caa0311ac..edec2691e 100644 --- a/transcode/manifest_test.go +++ b/transcode/manifest_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/grafov/m3u8" - "github.com/livepeer/catalyst-api/clients" "github.com/stretchr/testify/require" ) @@ -106,18 +105,22 @@ func TestItCanGenerateAndWriteManifests(t *testing.T) { masterManifestURL, err := GenerateAndUploadManifests( *sourceMediaPlaylist, outputDir, - []clients.EncodedProfile{ + []RenditionStats{ { - Name: "lowlowlow", - FPS: 60, - Width: 800, - Height: 600, + Name: "lowlowlow", + FPS: 60, + Width: 800, + Height: 600, + Bytes: 1, + DurationMs: 8000, }, { - Name: "super-high-def", - FPS: 30, - Width: 1080, - Height: 720, + Name: "super-high-def", + FPS: 30, + Width: 1080, + Height: 720, + Bytes: 1, + DurationMs: 8000, }, }, ) diff --git a/transcode/transcode.go b/transcode/transcode.go index 775f0eed9..7d1fdc87b 100644 --- a/transcode/transcode.go +++ b/transcode/transcode.go @@ -81,6 +81,7 @@ func RunTranscodeProcess(transcodeRequest TranscodeSegmentRequest, streamName st targetOSURL := segmentedUploadURL.ResolveReference(relativeTranscodeURL) // Grab some useful parameters to be used later from the TranscodeSegmentRequest sourceManifestOSURL := transcodeRequest.UploadURL + // transcodeProfiles are desired constraints for transcoding process transcodeProfiles := transcodeRequest.Profiles // If Profiles haven't been overridden, use the default set @@ -115,6 +116,10 @@ func RunTranscodeProcess(transcodeRequest TranscodeSegmentRequest, streamName st // Generate a unique ID to use when talking to the Broadcaster manifestID := "manifest-" + config.RandomTrailer(8) + // transcodedStats hold actual info from transcoded results within requested constraints (this usually differs from requested profiles) + transcodedStats := statsFromProfiles(transcodeProfiles) + + // Iterate through the segment URLs and transcode them // Use channel to queue segments queue := make(chan segmentInfo, len(sourceSegmentURLs)) for segmentIndex, u := range sourceSegmentURLs { @@ -130,7 +135,7 @@ func RunTranscodeProcess(transcodeRequest TranscodeSegmentRequest, streamName st go func() { defer completed.Done() for segment := range queue { - err := transcodeSegment(segment, streamName, manifestID, transcodeRequest, transcodeProfiles, targetOSURL) + err := transcodeSegment(segment, streamName, manifestID, transcodeRequest, transcodeProfiles, targetOSURL, transcodedStats) if err != nil { errors <- err return @@ -152,7 +157,7 @@ func RunTranscodeProcess(transcodeRequest TranscodeSegmentRequest, streamName st } // Build the manifests and push them to storage - manifestManifestURL, err := GenerateAndUploadManifests(sourceManifest, targetOSURL.String(), transcodeProfiles) + manifestManifestURL, err := GenerateAndUploadManifests(sourceManifest, targetOSURL.String(), transcodedStats) if err != nil { return outputs, err } @@ -172,7 +177,7 @@ func RunTranscodeProcess(transcodeRequest TranscodeSegmentRequest, streamName st return outputs, nil } -func transcodeSegment(segment segmentInfo, streamName, manifestID string, transcodeRequest TranscodeSegmentRequest, transcodeProfiles []clients.EncodedProfile, targetOSURL *url.URL) error { +func transcodeSegment(segment segmentInfo, streamName, manifestID string, transcodeRequest TranscodeSegmentRequest, transcodeProfiles []clients.EncodedProfile, targetOSURL *url.URL, transcodedStats []RenditionStats) error { rc, err := clients.DownloadOSURL(segment.Input.URL) if err != nil { return fmt.Errorf("failed to download source segment %q: %s", segment.Input, err) @@ -214,6 +219,9 @@ func transcodeSegment(segment segmentInfo, streamName, manifestID string, transc if err != nil { return fmt.Errorf("failed to upload master playlist: %s", err) } + // bitrate calculation + transcodedStats[renditionIndex].Bytes += int64(len(transcodedSegment.MediaData)) + transcodedStats[renditionIndex].DurationMs += float64(segment.Input.DurationMillis) } return nil } @@ -258,3 +266,25 @@ type segmentInfo struct { Input SourceSegment Index int } + +func statsFromProfiles(profiles []clients.EncodedProfile) []RenditionStats { + stats := []RenditionStats{} + for _, profile := range profiles { + stats = append(stats, RenditionStats{ + Name: profile.Name, + Width: profile.Width, // TODO: extract this from actual media retrieved from B + Height: profile.Height, // TODO: extract this from actual media retrieved from B + FPS: profile.FPS, // TODO: extract this from actual media retrieved from B + }) + } + return stats +} + +type RenditionStats struct { + Name string + Width int64 + Height int64 + FPS int64 + Bytes int64 + DurationMs float64 +}