-
Notifications
You must be signed in to change notification settings - Fork 6k
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
[Feature Request] Add support for ReplayGain #9796
Comments
I actually don't really know what your request is asking for in detail I'm afraid. :)
If you let me know what tags this is refering too in which media format, the I can probably tell how you can access these tags so you can implement support for ReplayGain yourself. You probably also want to clarify what you mean by 'natively honor`. If this would be adjusting the volume according to the tags found, then you can probably do this yourself by listening to the tags and then using the API to change the player volume. So either way, to implement this yourself or to give us a bit more information of what this would mean, you should provide us with some more information. |
Thank you @marcbaechinger for your reply, and sorry for not being clear enough, so here's some more info: First of all, I'm no Android developer, but just a music app user. I thought I understood that many Android music apps are basically some kind of front-end to ExoPlayer that handle the media loading/playing/etc. And I thought that tag-reading was part of that too, and that if ExoPlayer doesn't read ReplayGain tags, then the music apps built upon it won't be able to apply the ReplayGain tags (that's why apps like Vanilla Music and Foobar2000 Mobile had to write their own tag-reader). |
According to the Wikipedia article you linked, the tag is an 8-byte field that is delivered with a tag of the given format. Different audio formats have different ways of including these tags in the binary file. The article mentions FLAC, Vorbis and MP3. I think we deliver tags for these formats already via While I see the point in your request it is very unspecific. I can't remember developers asking for supporting this here on GitHub. This can be because there is no interest in this, or there are not many audio files taged like this. It can also be that it's just already possible to get the tags from the metadata output of ExoPlayer and apply the volume change. I leave this issue open and mark it as an enhancement. However, it's unlikely that we support this out of the box so that it is just applied by the library. That's more of an app responsibility to do. If we get from developers that the tag can't be read for a certain audio format they are using in their app, we may look into this to provide access to the tag and make it possible for apps to support it. |
@marcbaechinger Hey, I'm a developer of a music app that would benefit from this support, so I'm trying to implement this feature. What you said about val audioRenderer = RenderersFactory { handler, _, audioListener, _, _ ->
arrayOf(
MediaCodecAudioRenderer(this, MediaCodecSelector.DEFAULT, handler, audioListener),
MetadataRenderer(
MetadataOutput {
Log.d("MetadataOutput", "Metadata received")
},
handler.looper
)
)
}
val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)
return ExoPlayer.Builder(this, audioRenderer)
.setMediaSourceFactory(DefaultMediaSourceFactory(this, extractorsFactory))
.build() But this doesn't seem to call the |
Can you add your media to the demo app and then see if the metadata is reported? You would see this in the logs of the demo app or when placing a breakpoint in the Alternatively you can post a link to the media here, explain what tags you expect so I can have a look. If you're unable to share test content publicly, please send them to dev.exoplayer@gmail.com using a subject in the format "Issue #9796". Please also update this issue to indicate you’ve done this. |
Metadata is not reported on the demo app either, but any audio/video |
Can you please send us a sample URI to a stream and let me know what metadata you expect? I would expect that If you're unable to share bug reports or test content publicly, please send them to dev.exoplayer@gmail.com using a subject in the format "Issue #9796". |
Note that By the description of this ReplayGain metadata, it sounds like static ID3-like metadata, so I suspect it's expected that it doesn't get delivered to If you want to receive a callback when either the static or dynamic metadata changes (either because dynamic metadata arrives, or the track changes to one with different static metadata) then From the info provided I'm not convinced there's a bug here, so I'm going to mark this back as an enhancement. |
Oh. I assumed |
Can you provide me with a media file I can try with? |
As in media with replaygain tags, or media with |
I would be interested to see specific ReplayGain streams.
Does this work for you? |
Huh. I'd expect |
You and icbaker are probably right. That's why I wanted to have a media piece for testing. I don't have much experience with static metadata to be honest. The ID3 tags I have worked with have been delivered as in-band metadata that was received as timed metadata in HLS and I mean to remember this was similar for artworks in audio files. However, I'm probably wrong as I said. :) Lets check as soon as you posted some content here. |
Sounds good. Let me source an actual ReplayGain file from one of my users. |
Heres some sample files. Sorry for taking so long, I was busy with other projects. I did test these files, and |
Okay, I've created a ReplayGain implementation that works with both ID3v2 and FLAC [In the form of R128], largely derived from Vanilla Music. The overall code is below: import android.util.Log
import androidx.core.math.MathUtils
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.metadata.Metadata
import com.google.android.exoplayer2.metadata.flac.VorbisComment
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame
import kotlin.math.pow
const val RG_TRACK = "REPLAYGAIN_TRACK_GAIN"
const val RG_ALBUM = "REPLAYGAIN_ALBUM_GAIN"
const val R128_TRACK = "R128_TRACK_GAIN"
const val R128_ALBUM = "R128_ALBUM_GAIN"
val replayGainTags = arrayOf(
RG_TRACK,
RG_ALBUM,
R128_ALBUM,
R128_TRACK
)
data class Gain(val track: Float, val album: Float)
/**
* Applies replaygain to an ExoPlayer instance. This is based off Vanilla Music's implementation.
*/
fun applyReplayGain(player: ExoPlayer, metadata: Metadata) {
val gain = parseReplayGain(metadata)
// Currently we consider both the album gain first. One might want to add
// configuration to handle more cases.
var adjust = 0f
if (gain != null) {
adjust = if (gain.album != 0f) {
gain.album
} else {
gain.track
}
}
// Final adjustment along the volume curve.
// Ensure this is clamped to 0 or 1 so that it can be used as a volume.
adjust = MathUtils.clamp((10f.pow((adjust / 20f))), 0f, 1f)
Log.d("applyReplayGain", "Applying ReplayGain adjustment: $adjust")
player.volume = adjust
}
private fun parseReplayGain(metadata: Metadata): Gain? {
data class GainTag(val key: String, val value: Float)
var trackGain = 0f
var albumGain = 0f
var found = false
val tags = mutableListOf<GainTag>()
for (i in 0 until metadata.length()) {
val entry = metadata.get(i)
// Sometimes the ReplayGain keys will be lowercase, so make them uppercase.
if (entry is TextInformationFrame && entry.description?.uppercase() in replayGainTags) {
tags.add(GainTag(entry.description!!.uppercase(), parseReplayGainFloat(entry.value)))
continue
}
if (entry is VorbisComment && entry.key.uppercase() in replayGainTags) {
tags.add(GainTag(entry.key.uppercase(), parseReplayGainFloat(entry.value)))
}
}
// Case 1: Normal ReplayGain, most commonly found on MPEG files.
tags.findLast { tag -> tag.key == RG_TRACK }?.let { tag ->
trackGain = tag.value
found = true
}
tags.findLast { tag -> tag.key == RG_ALBUM }?.let { tag ->
albumGain = tag.value
found = true
}
// Case 2: R128 ReplayGain, most commonly found on FLAC files.
// While technically there is the R128 base gain in Opus files, ExoPlayer doesn't
// have metadata parsing functionality for those, so we just ignore it.
tags.findLast { tag -> tag.key == R128_TRACK }?.let { tag ->
trackGain += tag.value / 256f
found = true
}
tags.findLast { tag -> tag.key == R128_ALBUM }?.let { tag ->
albumGain += tag.value / 256f
found = true
}
return if (found) {
Gain(trackGain, albumGain)
} else {
null
}
}
private fun parseReplayGainFloat(raw: String): Float {
return try {
raw.replace(Regex("[^0-9.-]"), "").toFloat()
} catch (e: Exception) {
0f
}
} It doesn't handle every case, notably OPUS files have a volume gain adjustment in their header that should also be handled, but ExoPlayer doesn't parse that metadata. Feel free to use my code however you want. Personally, I think ReplayGain would be best left to the developer to implement simply due to how obscure the use-case is. But then again, that's what they also said about ICY streams, so I guess it's up to the maintainers. |
Also, a quick request: It would be quite helpful to access metadata from files other than ID3v2 or FLAC. OGG is pretty widely used and contains effectively the same metadata as FLAC, but ExoPlayer doesn't seem to parse it. Edit: I just enabled OGG support. Turns out ExoPlayer already parses it but doesn't expose it. You can find it in my fork. |
Hey @breversa. We need more information to resolve this issue but there hasn't been an update in 14 weekdays. I'm marking the issue as stale and if there are no new updates in the next 7 days I will close it automatically. If you have more information that will help us get to the bottom of this, just add a comment! |
Since there haven't been any recent updates here, I am going to close this issue. @breversa if you're still experiencing this problem and want to continue the discussion just leave a comment here and we are happy to re-open this. |
Hi !
Dozens of Android audio players, but only a handful of them actually support ReplayGain, and none of them are ExoPlayer-based AFAIK.
So would implementing ReplayGain support in ExoPlayer allow the batch of ExoPlayer-based players (such as OxygenCobalt/Auxio#7 ?) to natively honor ReplayGain tags (especially R128 for FLAC) ?
The text was updated successfully, but these errors were encountered: