Transformer: transmuxed Opus audio of a later clipped item in a sequence keeps pre-roll from before the clip start, duplicating audio at the junction
I maintainer di solito rispondono entro 5 giorni
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 22/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Ambito
- audio-video-rtc, mobile-dev
Direzione di ricerca
Start with ExoAssetLoaderAudioRenderer.shouldDropInputBuffer and EncodedSampleExporter.queueInputBuffer, where the issue traces pre-roll samples with negative timeUs into the previous item's range, and with TransformerUtil.shouldTranscodeAudio, which ignores clipping when transmuxing. Reproduce with the linked input.webm and a two-item clipped sequence. Done means the transmuxed output has 300 Opus packets with strictly increasing timestamps, matching the setTransmuxAudio(false) output.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Version
Media3 pre-release (alpha, beta or RC not in this list)
More version details
1.12.0-alpha02 (the outputs in the repro repo). 1.11.1 produces the same audio packets and timestamps. Its files additionally carry the already-fixed dOps pre-skip issue (#3431), which is unrelated.
Devices that reproduce the issue
- API 37 arm64 emulator (Google Play image): the repro repo's outputs, on 1.12.0-alpha02 and 1.11.1.
- Pixel 9 (API 37) and an API 26 arm64 emulator, on 1.11.1: the same duplication at the junction, with a different 10 s VP9 + Opus WebM input.
Devices that do not reproduce the issue
None found.
Reproducible in the demo app?
Not tested
Reproduction steps
1. Generate the input, or download input.webm from https://github.com/PierfrancescoSoffritti/media3-transmux-opus-preroll-repro. It is a 6 s VP9 + Opus WebM with video keyframes at 0 s and 3 s. The audio is a rising tone (300 Hz + 100 Hz/s), so its pitch identifies the source time and a repeat is easy to hear. Built with ffmpeg 9.0.2:
ffmpeg -f lavfi -i "testsrc2=size=640x360:rate=30" \
-f lavfi -i "aevalsrc='0.5*sin(2*PI*(300*t+50*t*t))':s=48000" -t 6 \
-c:v libvpx-vp9 -b:v 1M -g 90 -deadline realtime -cpu-used 8 -pix_fmt yuv420p \
-c:a libopus -b:a 64k input.webm
The file has 301 Opus packets of 20 ms each; the last one ends with discard padding. Its Cues index only the video track: there are two CuePoints, at 0 ms and 3000 ms, both on the VP9 track.
2. Export two adjacent clips of that file as one sequence, with audio transmuxed. This uses androidx.media3:media3-transformer with default settings. inputUri is a file:// URI of input.webm, outputPath is a writable path such as filesDir/output-transmuxAudio-true.mp4, and the code runs on the main looper.
fun clip(startMs: Long, endMs: Long) = EditedMediaItem.Builder(
MediaItem.Builder()
.setUri(inputUri)
.setClippingConfiguration(
MediaItem.ClippingConfiguration.Builder()
.setStartPositionMs(startMs)
.setEndPositionMs(endMs)
.build()
)
.build()
).build()
val composition = Composition.Builder(
EditedMediaItemSequence.withAudioAndVideoFrom(listOf(clip(0, 4_000), clip(4_000, 6_000)))
)
.setTransmuxAudio(true)
.build()
Transformer.Builder(context)
.addListener(listener) // inspect the output after onCompleted
.build()
.start(composition, outputPath)
The export completes successfully.
3. Repeat with only .setTransmuxAudio(false) changed, writing output-transmuxAudio-false.mp4.
4. Inspect both audio tracks:
ffprobe -v error -select_streams a -count_packets -show_entries stream=nb_read_packets -of csv=p=0 output-transmuxAudio-true.mp4
ffmpeg -v error -i output-transmuxAudio-true.mp4 -map 0:a -f null -
You can also listen to both files around 4 s.
Expected result
The audio track holds source 0–6 s once: 300 Opus packets with strictly increasing timestamps, and a tone that rises continuously for 6 s.
output-transmuxAudio-false.mp4 shows exactly that. It has 300 packets, ffmpeg reports no errors, and an ffmpeg decode gives 5.994 s of audio that follows the source throughout.
Actual result
In output-transmuxAudio-true.mp4, the second clip's audio starts at the 3 s keyframe instead of at its 4 s clip start. The extra second, 50 packets, lands inside the first clip.
-
Packet count:
ffprobereports 350 audio packets instead of 300. -
ffmpeg errors: ffmpeg prints 50 lines like
Application provided invalid, non monotonically increasing dts to muxer in stream 0: 144048 >= 144048. -
Duplicated timestamps: 50 timestamps, from 3.001 s through 3.981 s at 20 ms spacing, each occur twice. Here is an excerpt of ffprobe
pts_timearound the junction:2.901 2.921 2.941 2.961 2.981 3.001 3.001 3.021 3.021 3.041 3.041 3.061 3.061 … -
Decoded audio: an ffmpeg decode of the audio gives 6.994 s for a 6.0 s file. Mapping the tone frequency back to source time, it plays source 0–4.0 s, then source 3.0–4.0 s again, then 4.0–6.0 s.
The duplicated span is the distance from the clip start back to the previous keyframe cue, 1 s in this example.
Additional observations, not covered by the repro files:
-
ExoPlayer playback (1.11.1, PCM captured with a
TeeAudioProcessor, video track disabled):- It plays the same 1 s repeat. That makes 6.99 s of audio, and the final position is 7005 ms against a duration of 6001 ms.
- Audio after the junction plays 1 s later than its place in the timeline.
- The workaround output plays 5.99 s.
-
VLC plays the overlapping range as interleaved fragments of both copies.
-
Other containers (1.11.1, same export setup):
Input Result Vorbis in WebM Same duplication Opus in MKV Same duplication Opus in MP4, clip start not on a packet boundary One overlapping packet AAC-LC in MKV Clean -
First item vs later items: in our tests, only items after the first show this. For a single clipped item, the pre-roll is at the start of the output and is covered by an edit list.
Analysis (1.11.1)
This is our reading of the code. The cue layout above is consistent with it.
- The seek lands on the video keyframe. The second item is prepared at its 4.0 s clip start. The Matroska seek map is built from the Cues, and the only cue at or before 4.0 s is the 3.0 s video keyframe. So reading resumes from that cluster for all tracks.
- Opus keeps its pre-roll.
SampleQueuediscards samples before the start time only whencanDiscardAllSamplesToStartTimeholds. That requires an audio format for whichMimeTypes.allSamplesAreSyncSamples(...)is true, such as AAC-LC with a recognized codec string, MP3 or FLAC. It is false for Opus and Vorbis, which need pre-roll to decode. So the Opus samples from 3.0 s reach the asset loader. - The transmux path doesn't drop them.
ExoAssetLoaderAudioRenderer.shouldDropInputBuffersubtractsstreamStartPositionUs, which leaves these samples between −1.0 s and 0. It drops them onlyif (decoder != null && inputBuffer.timeUs < 0), that is, only when transcoding. - The item offset puts them inside the previous item.
EncodedSampleExporter.queueInputBufferaddsmediaItemOffsetUs + initialTimestampOffsetUs, so these samples land at 3.0–4.0 s, on top of the first item's last second.
ExoAssetLoaderAudioRenderer.shouldDropInputBuffer and EncodedSampleExporter.queueInputBuffer are unchanged on main.
For multi-item sequences, TransformerUtil.shouldTranscodeAudio returns !composition.transmuxAudio and doesn't consider clipping. The video-side check, TransformerInternal.clippingRequiresTranscode, is applied to the sequence's first item only.
Workaround: use setTransmuxAudio(false) when any item after the first starts after 0. Verified for this composition: when the audio is decoded, the negative-timestamp buffers are dropped before decoding.
A fix could drop, or transcode, transmuxed audio that precedes the start of an item after the first. We'll leave the right trade-off to you.
Media
https://github.com/PierfrancescoSoffritti/media3-transmux-opus-preroll-repro
input.webm: the synthetic input; the ffmpeg command in step 1 regenerates it.output-transmuxAudio-true.mp4: the affected output (1.12.0-alpha02).output-transmuxAudio-false.mp4: the workaround output (1.12.0-alpha02).
Bug Report
No response
- Lingua principale
- Java
- Stelle
- 3.1k
- Fork
- 1k
- Merge medio
- 7g 16h
- PR unite (30g)
- 6
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di androidx/media
-
CompositionPlayer.release() leaks its playback thread and FrameProcessor when no composition was setAperta
Difficoltà 2/5 Mezza giornata Idoneità per principianti 62/100
I maintainer di solito rispondono entro 5 giorni
-
enhancement needs triage
Difficoltà 3/5 1-2 giorni Idoneità per principianti 40/100
I maintainer di solito rispondono entro 5 giorni
-
Media3 media session not controllable by other apps since 1.11.0Forse già presa @tonihei l’ha presa 1 giorno fa. Apertabug needs triage
androidx/media#3507 · 1 assegnatario ·
I maintainer di solito rispondono entro 5 giorni
-
Allow Applying Effects to GAP itemsForse già presa @snow2405 l’ha presa 1 giorno fa. Apertaediting question
androidx/media#3505 · 1 assegnatario ·
I maintainer di solito rispondono entro 5 giorni
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 45/100
androidx/media#3504 · 1 commento ·
I maintainer di solito rispondono entro 5 giorni
Tutte le issue di androidx/media
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
wildfly/wildfly-channel#446 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
I maintainer di solito rispondono entro 1 giorno
-
5.0.x backport enhancement
Difficoltà 2/5 1-3 ore Idoneità per principianti 63/100
spring-cloud/spring-cloud-function#1507 ·
I maintainer di solito rispondono entro 1 giorno
-
Empty blood pressure handled as “null” string in exam editForse già presa Una pull request collegata a questa issue è aperta o già unita. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
open-learning-exchange/myplanet#18484 ·
I maintainer di solito rispondono entro 1 giorno
-
ELProcessor.defineFunction() fails for valid method signaturesForse già presa @RaphaelFakhri l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
jakartaee/expression-language#492 ·