Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New streaming model #3577

Merged
merged 135 commits into from
Dec 27, 2023
Merged

New streaming model #3577

merged 135 commits into from
Dec 27, 2023

Conversation

toots
Copy link
Member

@toots toots commented Dec 1, 2023

This PR implements the long-await change of our content-generation API.

With these changes, frames, our internal notion of content chunks being prepared for output, become variable-length small collections of tracks.

This should finally moves us away from the fantastic footgun that our current breaks/partial fill for track marks was.

Notes

Because track marks could appear before as part of the runtime streaming cycles, they were often implicit. For instance, if a source failed, a track mark would implicitly be added.

With the new API, we have to manually specify each track marks. This leads to situations were we don't really have a convention for. Typically, should all sources emit an initial track when starting?

If we want to be true to our muxers, it should be possible to drop all track marks, thus the answer to the above question is no. However, we have relied in the past on operators such as on_track to detect when a source becomes available again. this would also require to change existing code and, most likely, introduce a new operator.

Most of the current work was focused on stabilizing our current set of tests. Next, we shall cleanup and implement the new conventions and operators that we need.

Changed operators

The following operators are directly impacted by these changes:

  • crossfade: the non-conservative mode has been removed. It was already complicated enough to keep the conservative mode. Other than that, the operator is expected to operate as close as possible to the the existing one.
  • source.dynamic, switch and sequence: these operators , the only internal ones which deal with dynamic selection of source within a streaming cycle, have been rewritten to use a factored-out Source.generate_from_multiple_sources class. This class is charged with capturing all the logic pertaining to composing a full frame from multiple sources. Although tricky to finalize, it seems to be satisfying. Most of the tricks were about:
    • Triggering on_track handlers at the right time when switching sources to allow implemtations based on them such as rotate, random etc to update their internal state accordingly.
    • Knowing when to allow the operator to select a new source or keep the existing one based on partial fill and knowing when to consider a track mark a freshly selected source new track or an already selected source end of track, triggering the re-selection of a new track.

New API

The API for generating frames becomes, for sources:

       (** Sources must implement this method. It should return [true] when
           the source can produce data during the current streaming cycle. *)
       method virtual private can_generate_frame : bool

       (** Sources mushc implement this method. It should return the data
           produced during the current streaming cycle. Sources are responsible
           for producing as much data as possible, up-to the frame size setting. *)
       method virtual private generate_frame : Frame.t

       (** This method is based on [can_generate_frame] and has the same value through
           the whole streaming cycle. *)
       method is_ready : bool

       (** If the source is ready, this method computes the frame generated by the
           source during the current streaming cycle. Returned value is cached and should
           be the same throughout the whole streaming cycle. *)
       method get_frame : Frame.t

       (** This method passes the frame returned by [#get_frame] to the given callback.
           The callback should return the portion of the frame (of the form: [start, end))
           that was effectively used. This method is used when a consumer of the source's data
           only uses an initial chunk of the frame. In this case, the remaining data is cached
           whenever possible and returned during the next streaming cycle. Final returned value
           is the same as the partial chunk returned for the callback for easy method call chaining. *)
       method get_partial_frame : (Frame.t -> Frame.t) -> Frame.t

       (** This method requests a specific field of the frame that can be mutated. It used used
           by a consumer of the source that will modify the source's data (e.g. [amplify]). The
           source will do its best to minimize data copy according to the streaming context. Typically,
           if there is only one consumer of the source's data, it should be safe to pass its
           data without copying it. *)
       method get_mutable_content : Frame.field -> Content.data

       (** This method is the same as [#get_mutable_content] but returns a full frame with the request
           mutable field included. *)
       method get_mutable_frame : Frame.field -> Frame.t

       (** By convention, frames produced during the streaming cycle can only have at most one
           track mark. In case of multiple track marks (which most likely indicate a programming
           problem), all subsequent track marks past the first one are dropped.

           This function returns a pair: [(initial_frame, new_track option)] of an initial frame
           and, if a track mark is present in the frame, the optional portion of the frame contained
           after this track mark.

           This method is pretty convenient to implement operations that should be aware of new tracks. *)
       method private split_frame : Frame.t -> Frame.t * Frame.t option

TODO

  • Proof read
  • Test operators
  • Update doc

Future work

  • We should enforce that sources can produce at least some data when they return can_generate_frame. This needs some adjustments with synchronous operators such as input.ffmpeg
  • Frames do not have to contain at least one video frame. We should be able to support frames of any arbitrary length.
  • Reimplement clocks. Currently, get_mutated_field is not optimized yet. This requires comme coordination with the clock (e.g. to know what operators are consuming a given source's data during the current streaming cycle) that should be implemented when reworking the clocks.

@toots toots force-pushed the new-streaming branch 5 times, most recently from 3a1cf55 to 214c5f6 Compare December 6, 2023 23:57
@toots toots force-pushed the new-streaming branch 13 times, most recently from af986d4 to 6b93328 Compare December 12, 2023 01:04
@toots
Copy link
Member Author

toots commented Dec 27, 2023

Ok, I've run a comparison test between main and this PR with the complex script that generates this VJ stream:

Screenshot 2023-12-27 at 9 32 23 AM

Memory and CPU profiles are pretty similar with a little saving on the new streaming side it seems. Overall, this is ready for merge. We might be able to save more memory allocations once we allow the frame to go down below one single video frame. Going from 4069 audio samples to 1024 could divide by 4 the amount of memory allocated in source frames!

main branch

CPU

Screenshot 2023-12-26 at 6 50 34 PM

Memory allocations

This value always feels not very reliable:

Screenshot 2023-12-26 at 6 50 39 PM

This seems more accurate:

Screenshot 2023-12-26 at 6 50 45 PM Screenshot 2023-12-27 at 9 36 27 AM

new-streaming branch

CPU

Screenshot 2023-12-26 at 6 53 57 PM

Memory allocations

Screenshot 2023-12-26 at 6 54 03 PM Screenshot 2023-12-26 at 6 54 10 PM Screenshot 2023-12-27 at 9 37 52 AM

@toots toots enabled auto-merge December 27, 2023 15:42
@toots toots added this pull request to the merge queue Dec 27, 2023
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Dec 27, 2023
@toots toots added this pull request to the merge queue Dec 27, 2023
@toots toots removed this pull request from the merge queue due to a manual request Dec 27, 2023
@toots toots enabled auto-merge December 27, 2023 16:21
@toots toots added this pull request to the merge queue Dec 27, 2023
Merged via the queue into main with commit 77cf76e Dec 27, 2023
26 checks passed
@toots toots deleted the new-streaming branch December 27, 2023 17:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant