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

Optimize loading of MP4 on slow media sources #175

Open
pdeljanov opened this issue Jan 20, 2023 · 10 comments
Open

Optimize loading of MP4 on slow media sources #175

pdeljanov opened this issue Jan 20, 2023 · 10 comments
Labels
enhancement New feature or request

Comments

@pdeljanov
Copy link
Owner

pdeljanov commented Jan 20, 2023

The initial scan of top-level atoms in IsoMp4Reader::try_new can be slow if the MediaSource is slow to read and seek. This is particularly bad with fragmented MP4s where there are many MOOF + MDAT pairs. This issue tracks possible optimizations:

  1. AtomIterator::next calls ignore_bytes to skip the body of an unread atom. It should be possible to replace ignore_bytes with an explicit seek if the reader is seekable. However, there will still be a lot of seek + small read operations as all the atoms are skipped over.
  2. If the MOOV and FTYP atoms were already encountered, it may be reasonable to terminate scanning of the top-level atoms at the first MOOF or MDAT atom. There'd need to be some documentation (i.e., standards, etc.) or proof that this is a good general optimization that won't break anything. However, this would still defer loading the fragments to the first seek.
  3. Use the SIDX atom (segment index) to avoid the scanning that would happen on a seek if the 2nd point was implemented.

This issue was promoted by the discussion in #153.

@pdeljanov pdeljanov added the enhancement New feature or request label Jan 20, 2023
@erikas-taroza
Copy link
Contributor

From #153

If I understand correctly, it sounds like the MediaSource works properly now and the only problem is that loading the MP4 is slow?

Streaming a normal MP4 works fine. The loading of a fragmented MP4 is slow because of the chunk requests.

However, there will still be a lot of seek + small read operations as all the atoms are skipped over..

I believe the atom skipping is what is causing the HTTP requests to happen, making the loading slow. Therefore, I do not think solution 1 will help (in the case of streaming a fragmented MP4).

@pdeljanov
Copy link
Owner Author

However, there will still be a lot of seek + small read operations as all the atoms are skipped over..

I believe the atom skipping is what is causing the HTTP requests to happen, making the loading slow. Therefore, I do not think solution 1 will help (in the case of streaming a fragmented MP4).

Probably not by much.

You can try this to see if optimization 2 helps. In this match case:

AtomType::MediaData | AtomType::MovieFragment => {
// The mdat atom contains the codec bitstream data. For segmented streams, a
// moof + mdat pair is required for playback. If the source is unseekable then
// the format reader cannot skip past these atoms without dropping samples.
if !is_seekable {
// If the moov atom hasn't been seen before the moof and/or mdat atom, and
// the stream is not seekable, then the mp4 is not streamable.
if moov.is_none() || ftyp.is_none() {
warn!("mp4 is not streamable.");
}
// The remainder of the stream will be read incrementally.
break;
}
}

Replace the contents with:

if moov.is_some() && ftyp.is_some() {
    break;
}

Loading should be much faster.

@erikas-taroza
Copy link
Contributor

With that change, initial loading does not download the whole file (good!). When seeking, ignore_bytes is called and it reads until the seek position is found.

@pdeljanov
Copy link
Owner Author

With that change, initial loading does not download the whole file (good!). When seeking, ignore_bytes is called and it reads until the seek position is found.

Yes, as mentioned, this defers further scanning to when it seeks. However, you only have to scan from current position to the required position so still a net positive. If implemented, the 1st item should speed the scan up further if you want to try. Of course, the 3rd item would be best, but that's a non-trivial change to even prototype.

@erikas-taroza
Copy link
Contributor

If implemented, the 1st item should speed the scan up further if you want to try.

I can give it a go.

@erikas-taroza
Copy link
Contributor

erikas-taroza commented Jan 20, 2023

Hello @pdeljanov

This is what I got. Let me know if you have any suggestions with the way I implemented it.

I used std::time::Instant to test the difference. Seeking takes 0ms and calling ignore_bytes takes about 300-400ms. It was taking around 75-80ms every time it was skipping.

edit: I just made some additional changes here.

@pdeljanov
Copy link
Owner Author

I don't think this is how we would implement it since ReadBytes shouldn't have any seek functionality (it's a stream), but it seems reasonable for experimentation. Does this solve the problem for you?

@erikas-taroza
Copy link
Contributor

I don't think this is how we would implement it since ReadBytes shouldn't have any seek functionality (it's a stream), but it seems reasonable for experimentation. Does this solve the problem for you?

Yeah its a naive implementation. It doesn't completely solve the problem though. I think using the SIDX atom would be the final step. Unfortunately, I do not know how to implement it.

@pdeljanov
Copy link
Owner Author

Yeah, so to recap: the file loads instantly now, but you're seeing multiple seeks as we jump from fragment to fragment until the timestamp is reached?

The SIDX atom would help here I think. Though, it is hard to implement and may raise other issues during implementation. Unfortunately, it will take some time.

Another thing you could do right now is reuse your HTTP connection(s). I think a lot of time is likely being spent setting up a new connection (especially with SSL) so each seek becomes very expensive. Better yet, if QUIC is offered by the server, it'll be even faster.

@erikas-taroza
Copy link
Contributor

Yeah, so to recap: the file loads instantly now, but you're seeing multiple seeks as we jump from fragment to fragment until the timestamp is reached?

Yes, this is the current behavior.

Another thing you could do right now is reuse your HTTP connection(s).

I will look into this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants