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

Ability to switch audio track during playback #514

Closed
IanDBird opened this issue Jun 2, 2015 · 22 comments
Closed

Ability to switch audio track during playback #514

IanDBird opened this issue Jun 2, 2015 · 22 comments

Comments

@IanDBird
Copy link
Contributor

IanDBird commented Jun 2, 2015

I'm attempting to add support to our video player that allows individual audio tracks to be selected during playback. Initially i'm focussing on my ExtractorRendererBuilder (with MP4s) but this really affects any media which can contain multiple different audio tracks.

First Issue

MediaCodecAudioTrackRenderer by default will select, and use, the first audio track provided by the SampleSource. It therefore seemed appropriate to extend this class and override the handlesMimeType used during onPrepare in order to try and "select" the audio stream that you want. A problem then arrises that "mimeType" is not a unique identifier within a video. For example, you could easily have an MP4 which has 2 AAC tracks, the main feature vs the directors commentary. In order to make it easier to identify individual tracks, it might be worth considering including the trackIndex into the method, for example:

protected boolean handlesMimeType(int trackIndex, String mimeType)

With this change in place, it should then be possible to create N individual audio track renderers (one for each known audio track), and to disable / enable them on demand.

Second Issue

There are two possible (related) issues with what I described above:

  • Are there any overheads associated with having lots of separate instances of MediaCodecAudioTrackRenderer?
  • What if you don't know up front how many audio tracks are available?

The audio tracks are available via the SampleSource but this is prepared after the MediaCodecAudioTrackRenderer's have been created. I therefore looked at what it would take to have a single MediaCodecAudioTrackRenderer that when we needed to "change" it (because we needed to select a new track index) we would attempt to re-use the original SampleSource but create a new instance of the audio track renderer. However, there doesn't appear to be any way to replace/substitute a new track renderer like this. As mentioned in my approach above, the handlesMimeType is called when being prepared. However, if you call the ExoPlayer.prepare a second time it will dispose of all the original renderers and eventually their SampleSource (that we're trying to re-use).

Is there something that i'm missing on how best to implement this?

@Ood-Tsen
Copy link
Contributor

Ood-Tsen commented Jun 3, 2015

It seems only SS and Dash are support switch between multiple audio tracks.

Those have implement MultiTrackChunkSource.

@ojw28
Copy link
Contributor

ojw28 commented Jun 3, 2015

Correct. Also, when we implement this for HLS, we'll use MultiTrackChunkSource there as well.

I think we're less interested in the ExtractorSampleSource case in general, since muxing multiple tracks of the same type into a single stream is fundamentally not the best approach (DASH or SS is always going to be better if you want multiple tracks of the same type). If you were to add support, however, doing it at the ExtractorSampleSource layer would be more consistent with the model used elsewhere. You shouldn't need multiple renderers. It should be possible to switch track by (1) pause, (2) disable renderer, (3) reconfigure sample source to provide samples for a different track, (4) re-enable renderer [if applicable], (5) unpause [if applicable]. Like:

https://github.com/google/ExoPlayer/blob/master/demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java#L600

Step (3) is something you'd need to implement.

@ojw28 ojw28 added the question label Jun 3, 2015
@IanDBird
Copy link
Contributor Author

IanDBird commented Jun 3, 2015

I completely understand and agree that there might be better formats out there which handle multiple tracks of the same type. However, since it's basically common practice to store multiple audio tracks within a single MP4/MKV/whatever, it does feel like a useful/important feature to support.

I've had a go at working through what you suggested, and still struggling a little. The renderer and selected track within the sample source are fairly tightly coupled, the MediaCodecTrackRenderer even stores the trackIndex as a local member. When the original renderer is "disabled" it will go ahead and disable the same associated track within the SampleSource. In order to ensure the MediaCodecTrackRender is initialised correctly when needing to select a different track, it needs to execute it's doPrepare again in order to allow someone to programmatically control the track to be selected (via handlesMimeType). But as I mentioned in the original description, even if this was called, handlesMimeType's interface isn't good enough to uniquely identify an actual stream.

If I start changing the ExoPlayer source to allow trackIndex to be modifiable, I guess I could do something like:

  • Disable the MediaCodecAudioTrackRenderer, which disables the current audio track inside the SampleSource
  • Set trackIndex on my MediaCodecAudioTrackRenderer instance to the new track i'd like to consume
  • Enable the MediaCodecAudioTrackRenderer

I'm sorry, but is there any other way to do this cleanly? I'm trying to avoid deviating from the standard ExoPlayer source as much as possible (to ensure it's easier to pick up changes).

@IanDBird
Copy link
Contributor Author

IanDBird commented Jun 3, 2015

Just a quick update, but I have got things working using the following code:

A custom audio codec renderer:

public class SelectableMediaCodecAudioTrackRenderer extends MediaCodecAudioTrackRenderer
{
  public static final int MSG_SELECT_TRACK = 1;

  public SelectableMediaCodecAudioTrackRenderer(
      SampleSource source,
      DrmSessionManager drmSessionManager,
      boolean playClearSamplesWithoutKeys,
      Handler eventHandler,
      EventListener eventListener)
  {
    super(source, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, eventListener);
  }

  @Override
  public void handleMessage(int what, Object msg) throws ExoPlaybackException
  {
    if (what == MSG_SELECT_TRACK)
      super.trackIndex = (Integer) msg;
  }
}

Code to switch the audio track:

      m_player.setPlayWhenReady(false);
      m_player.setRendererEnabled(TYPE_AUDIO, false);

      m_player.sendMessage(m_audioRenderer, SelectableMediaCodecAudioTrackRenderer.MSG_SELECT_TRACK, newStreamIndex);

      m_player.setRendererEnabled(TYPE_AUDIO, true);
      m_player.setPlayWhenReady(true);

The thing that's not possible without modifying the ExoPlayer source is the super.trackIndex = (Integer) msg;. Ideally, there would be a way for a subclass to programmatically select the desired track, as long as it was of the same type and also the renderer itself was disabled.

Would that change be considered?

@IanDBird
Copy link
Contributor Author

IanDBird commented Jun 4, 2015

This looks like a related issue: #168

@ojw28
Copy link
Contributor

ojw28 commented Jun 4, 2015

In my proposal there would be a single track exposed by the sample source, and the switching would be hidden behind it. The approach definitely works; if you look at how we do it for DASH/SS, it's following that approach.

Having said that, it seems things would be better if track switching were made more of a first class citizen of ExoPlayer. I think we should do the following, which should be good for what you need:

  1. Add a onPrepared() method to ExoPlayer.Listener, to be invoked once preparation has been completed (you can infer the same thing from the existing onPlayerStateChanged, but it's not trivial to do so).
  2. Add getTrackCount(), getTrackInfo(int index) and selectTrack(int index) (or similar - exact API TBC) to TrackRenderer.
  3. Add getTrackCount(int rendererIndex), getTrackInfo(int rendererIndex, int trackIndex) and selectTrack(int renderer, int trackIndex) to ExoPlayer.

We'll need to figure out what an appropriate TrackInfo object is. It will need to contain more than the current TrackInfo object.

To implement multi-track selection in an app you'd then:

  1. Wait for onPrepared, which indicates that track information is now available.
  2. Query the track information and display it in the UI.
  3. Call selectTrack(rendererIndex, trackIndex) to select tracks.

It may also make sense to merge setRendererEnabled into this new API. Disabling a renderer could be implemented by calling selectTrack(renderer, -1).

Thoughts?

@ojw28
Copy link
Contributor

ojw28 commented Jun 16, 2015

I have a version of this working, but it's pretty major surgery through the code-base. It'll likely be a few weeks until it's all merged.

@nahojkap
Copy link

Assume you are going to use 2 separate chunk sources for this use case? 1 for video and the MultiTrackChunkSource for the various audio tracks?

If so, there is a big risk the two will not correctly synchronize audio since the loading of playlists can (and will) happen at different times and under the right circumstances, the player will choose different start segments to start with.

For example:

Video playlist is loaded and contains sequence numbers 5,6,7,8,9,10 - playback will start with 8
Audio playlist is loaded and contains 6,7,8,9,10,11 - playback should start with 8 but code currently will pick 9.

So, there is a need to synchronize this somehow between the chunk sources. Perhaps you have already considered this but I thought I'd bring it up.

@ojw28
Copy link
Contributor

ojw28 commented Jun 17, 2015

This request is unrelated to HLS. Multi-audio in HLS is tracked by #73.

@nahojkap
Copy link

My bad - will the work you are doing here bring #73 closer to being implemented too? In any case, will re-post this there.

@ojw28
Copy link
Contributor

ojw28 commented Jun 17, 2015

It will provide plumbing for passing track information through common player components, so yes it will help.

@maxstone2012
Copy link

where download the source code that the MediaCodecAudioTrackRenderer Ability to switch audio track

@IanDBird
Copy link
Contributor Author

IanDBird commented Aug 4, 2015

Hey @ojw28, any updates on how this is progressing? If there is anything I can do to help out, please just let me know!

@ojw28
Copy link
Contributor

ojw28 commented Aug 5, 2015

We'll start merging some steps towards this soon. The first one will create a common base class for all TrackRenderer implementations that pull samples from a SampleSource, so as to avoid having to add multi-track propagation logic in multiple renderers separately. We'll then start merging code to actually propagate the tracks through the various components. Initially we'll be exposing fairly useless information about each track, although once we're at that point the task of populating the information properly can probably be parallelised to some extent.

@ShaoWeiguo
Copy link

BTW, when will it be done? We also need this feature.

ojw28 added a commit that referenced this issue Aug 11, 2015
This will allow multi-track support when consuming from a SampleSource to
be added in a single class, rather than in 6. Prep for Issue #514.
ojw28 added a commit that referenced this issue Aug 11, 2015
- Generalize rendererEnabledFlags to be selected track indices through
  ExoPlayerImpl/ExoPlayerImplInternal.
- Selecting an out-of-bound track index (e.g. -1) is equivalent to
  disabling a renderer prior to the generalization.
- A prepared TrackRenderer that exposes 0 tracks is equivalent to a
  TrackRenderer in the STATE_IGNORE state prior to the generalization.

Issue #514.
ojw28 added a commit that referenced this issue Aug 11, 2015
ojw28 added a commit that referenced this issue Aug 17, 2015
multi-track support upstream to the ChunkSource interface.

This change does not yet make use of the newly exposed APIs. This
will come in a subsequent CL.

Issue #514.
ojw28 added a commit that referenced this issue Aug 17, 2015
When ChunkSource implementations implement multi-track for DASH and SS,
format selection will move inside of ChunkSource. If we, for example, fail
to query the decoder to determine which tracks are playable, we need an
opportunity to fail (i.e. say we're not prepared, so that maybeThrowError
is called, from which we can throw).

This may go away in the future if we remove the distinct preparation step
and treat tracks/formats as things that can change dynamically, but for now
this is what we have.

Issue #514.
ojw28 added a commit that referenced this issue Aug 19, 2015
- Migrate demo app to use new APIs.
- Add multi-track support for ExtractorSampleSource case.
- Add multi-track support for SmoothStreaming use case.

The final step is to add support back for the DASH use case and
delete MultiTrackChunkSource. This is blocked on multi-period support
landing, in order to prevent a horrendous merge conflict. We also
need to update HLS to expose sensible track information.

Issue: #514
ojw28 added a commit that referenced this issue Aug 19, 2015
@ojw28
Copy link
Contributor

ojw28 commented Aug 19, 2015

Status update:

  • This is fixed as far as MP4 is concerned. If you try and play an MP4 with multiple tracks in the demo app, the Audio button will now list each track separately, and allow you to switch between them.
  • For SmoothStreaming you'll see multiple audio tracks as before. For video, you'll now see both "auto" and each individual format. Previously it was not possible to switch to a fixed format, so this is new.
  • For DASH, multiple audio tracks have temporarily disappeared. They'll be back in due course, along with the ability to select individual video formats (i.e. equivalent to the SmoothStreaming case). This work is currently blocked on another feature being delivered.
  • HLS is the same as before, except that the video format exposed is currently wrong (it should probably be "auto"). We'll fix this.

@ojw28
Copy link
Contributor

ojw28 commented Aug 20, 2015

  • We will also be adding multi-track support for MKV/WebM (probably tomorrow).

@dnutcracker
Copy link

Does this means it will also be supported to mute the video while it's playing? (by switching to a null/empty audio track)

@andreimarinescu
Copy link

@dnutcracker: you should be able to achieve this already by doing something like this in your ExoPlayer interface (equivalent of the DemoPlayer class of the demo app):
player.sendMessage(audioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, 0f);

@ojw28
Copy link
Contributor

ojw28 commented Aug 21, 2015

This has been possible for a long time (the demo app has had this ability for a long time too). Using ExoPlayer's setRendererEnabled method is the best way to do it in the current release.

@dnutcracker
Copy link

Ok.Thanks.

On Fri, Aug 21, 2015 at 12:30 PM, ojw28 notifications@github.com wrote:

This has been possible for a long time (the demo app has had this ability
for a long time too). Using ExoPlayer's setRendererEnabled method is the
best way to do it in the current release.


Reply to this email directly or view it on GitHub
#514 (comment).

ojw28 added a commit that referenced this issue Sep 1, 2015
- Video track is always marked as adaptive, the resolution is
  stripped out (since it's otherwise just set to whatever the
  resolution of the first selected variant is), and the max
  dimensions are set.

Issue #514
ojw28 added a commit that referenced this issue Sep 1, 2015
@ojw28
Copy link
Contributor

ojw28 commented Sep 1, 2015

@ojw28 ojw28 closed this as completed Sep 1, 2015
@google google locked and limited conversation to collaborators Jun 28, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

8 participants