ExoPlayer icon indicating copy to clipboard operation
ExoPlayer copied to clipboard

Avoid audible glitch when clipping and re-concatenating a single audio source

Open Yahui-Li opened this issue 3 years ago • 16 comments

It works perfect when play a single .m4a file with ProgressiveMediaSource. But it seems not sealmess when play the same audio file with ClippingMediaSource and ConcatenatingMediaSource. For example

        val durations = longArrayOf(20100, 467, 11233, 7100, 6267, 10300)
        var start = 0L
        val audioFile = File(dir, "sample.m4a")
        val audioSource = DefaultMediaSourceFactory(context)
            .createMediaSource(MediaItem.fromUri(Uri.fromFile(audioFile)))
        val concatAudioSource = ConcatenatingMediaSource(true)
        for (i in 0 until durations.size) {
            val durationUs = durations[i] * 1000
            val end = start + durationUs
            val clipAudio = ClippingMediaSource(audioSource, start, end)
            concatAudioSource.addMediaSource(clipAudio)
            start = end
        }
        _player?.run {
            setMediaSource(concatAudioSource)
            prepare()
        }

then you will heard that an obvious pause(maybe glitch?, I'm not sure) between two period. It's easy to reproduce

Yahui-Li avatar Aug 20 '21 10:08 Yahui-Li

I've reproduced this in the demo app and can hear a playback glitch. I used the following JSON (which gets coverted to a playlist of 2 MediaItems, and this ends up being handled very similarly to ClippingMediaSource and ConcatenatingMediaSource):

{
  "name": "audio playlist",
  "playlist": [
    {
      "uri": "path to mp3",
      "clip_start_position_ms": 150000,
      "clip_end_position_ms": 160000
    },
    {
      "uri": "path to mp3",
      "clip_start_position_ms": 160000,
      "clip_end_position_ms": 170000
    }
  ]
},

@krocard can you take a look at what's happening here?

icbaker avatar Aug 23 '21 16:08 icbaker

Any progress?

Yahui-Li avatar Aug 26 '21 12:08 Yahui-Li

This is a relatively contrived use case. Clipping media source doesn't cut a resource at an exact pcm frame, which perfect gapless playback would need. ExoPlayer supports gapless playback, but the amount to clip needs to be small and present in the container, not the media source.

In general a player that would not introduce a ramp down and up on transitions would result in an audible glitch as the waveform would not be continuous (high frequency). Thus the current behaviour is desirable.

Could you tell us more about what you are trying to achieve?

krocard avatar Sep 09 '21 10:09 krocard

See, I want to merge a sequence of video resources and a single audio resource. For example, I have three video fragments each duration is 10 sec and an audio fragment that duration is 30 sec. Now I want to merge those resources together but I can't because the ConcatenatingMediaSource of videos had different period count with the audio MediaSource, so I cut the audio to three pieces then concatenate them so that I can merge the video and audio together. I can't find another way to handle this situation. Do you have any suggestion?

Yahui-Li avatar Sep 09 '21 11:09 Yahui-Li

You should use a MergingMediaSource to merge

  • the audio source
  • a ConcatenatingMediaSource of the video sources.

krocard avatar Sep 09 '21 16:09 krocard

@marcbaechinger Can you confirm if this is correct? MergingMediaSource has a restriction that each source should have the same number of periods, so I'm not sure it's compatible with ConcatenatingMediaSource.

krocard avatar Sep 09 '21 17:09 krocard

Yes, I can confirm that MergingMediaSource.createPeriod() expects that all sources have the same number of periods.

marcbaechinger avatar Sep 10 '21 10:09 marcbaechinger

So, What's the most appropriate way to solve this? I really need help.

Yahui-Li avatar Sep 13 '21 13:09 Yahui-Li

Thanks for the confirmation @marcbaechinger. So it seems my suggestion to concatenate the videos and then merge them with the audio will not work due to the difference in period. @marcbaechinger, given this limitation, what would be the best way to play multiple videos with continuous audio?

krocard avatar Sep 13 '21 14:09 krocard

I don't think that there is currently a solution for this with ExoPlayer media source composition for the reasons given above. I marked this issue as an enhancement. We need to look into this and see how we can provide a solution for this. We see several approaches how we can make this work, but I can't give you a concrete timeline for when that will happen I'm afraid.

An option to have this working properly in the mean time, would be to merge the media before you play it. You could for instance create a single video stream with the audio and the three video parts with ffmpeg or a similar tool and then play the video as a single media file with ExoPlayer. I understand that this is not a dynamic solution that you can apply easily on-the-fly with arbitrary videos, but if this use case is specific to these four media files this is probably the easiest way.

Sorry, for not being able to give you a better answer.

marcbaechinger avatar Sep 14 '21 10:09 marcbaechinger

+1

yogeshchoudhary-gts avatar Oct 12 '21 10:10 yogeshchoudhary-gts

And I tried that if I cut a resource at an exact pcm frame(I use atds as audio format), there will produce a glitch either. so I guess the reason why produce a glitch is not because ExoPlayer can not cut audio resource at an exact pcm frame but there has no pre-cache of next period when the active period is playing. But I'm not sure about that. I will read source code more deeply when I have time。

Yahui-Li avatar Dec 06 '21 09:12 Yahui-Li

the reason why produce a glitch

The reason is that ExoPlayer flushes the MediaCodec between the different audio resources. If you disable the flushing manually and keep the same codec instance, everything works smoothly without any audible glitches.

We have to flush the codec in the general case because we can't know whether feeding data from two audio sources to the same decoder consecutively is save (=doesn't result in some other form of audible glitch). So this enhancement is tracking to either:

  • keep the decoder in some cases where we can be sure it's safe (unclear if there is such a case)
  • signal to the decoder that two consecutive audio sources are continuous and can use the same decoder. This only makes sense in combination with ClippingMediaSource, so needed to be forwarded from this source somehow.
  • Support merging sources with different number of periods. This is the most flexible approach as it avoids clipping to start with. It doesn't fit very well into our media model though.

tonihei avatar Dec 06 '21 16:12 tonihei

Wow,thank you for your timely and clearly response. I'm appreciate it very much and it's very useful to me. I try to reuse the codec and It's seems perfect! Thanks, my friend.

Yahui-Li avatar Dec 07 '21 04:12 Yahui-Li

@YouheiLee @tonihei Would you be able to provide a sample on how to manually disable codec flushing? I have been struggling with this issue for a while and could not find a solution. Thank you

ShahyarTaheri avatar Jul 26 '22 20:07 ShahyarTaheri

I hackily changed this line to return REUSE_RESULT_YES_WITHOUT_RECONFIGURATION. This is only safe for this particular use case though and no other audio transition as pointed out above.

tonihei avatar Jul 27 '22 08:07 tonihei