fix(player): show audio codec and bitrate in the performance overlay on ExoPlayer

The Android performance overlay showed only sample rate and channels for
EAC3, FLAC, DTS, TrueHD, and Opus tracks. The overlay reads the codec
from media3's Format.codecs, an RFC 6381 string only MP4/HLS provide
(hence AAC working), and the bitrate from Format.bitrate, which Matroska
carries only when the muxer wrote BPS statistics tags.

Fall back to the already-transmitted sample MIME type for the codec name
(audio and video), and measure the audio bitrate in the FFmpeg demuxer
from packet sizes over their pts span — the same source mpv uses for its
audio-bitrate property — when the container declares none.

close #2063
This commit is contained in:
edde746
2026-08-23 03:44:45 +02:00
parent c201bc2f03
commit 3f55cc461e
8 changed files with 281 additions and 9 deletions
+19
View File
@@ -200,6 +200,25 @@ void main() {
expect(CodecUtils.formatAudioCodec('alac'), 'ALAC');
expect(CodecUtils.formatAudioCodec('weird'), 'WEIRD');
});
test('audio MIME types from ExoPlayer Format.sampleMimeType map to friendly names (#2063)', () {
expect(CodecUtils.formatAudioCodec('audio/mp4a-latm'), 'AAC');
expect(CodecUtils.formatAudioCodec('audio/mpeg'), 'MP3');
expect(CodecUtils.formatAudioCodec('audio/ac3'), 'AC3');
expect(CodecUtils.formatAudioCodec('audio/eac3'), 'E-AC3');
expect(CodecUtils.formatAudioCodec('audio/eac3-joc'), 'E-AC3');
expect(CodecUtils.formatAudioCodec('audio/true-hd'), 'TrueHD');
expect(CodecUtils.formatAudioCodec('audio/vnd.dts'), 'DTS');
expect(CodecUtils.formatAudioCodec('audio/vnd.dts.hd'), 'DTS-HD');
expect(CodecUtils.formatAudioCodec('audio/vnd.dts.hd;profile=lbr'), 'DTS-HD');
expect(CodecUtils.formatAudioCodec('audio/vnd.dts.uhd;audio=p2'), 'DTS:X');
expect(CodecUtils.formatAudioCodec('audio/flac'), 'FLAC');
expect(CodecUtils.formatAudioCodec('audio/opus'), 'Opus');
expect(CodecUtils.formatAudioCodec('audio/vorbis'), 'Vorbis');
expect(CodecUtils.formatAudioCodec('audio/ac4'), 'AC4');
expect(CodecUtils.formatAudioCodec('audio/raw'), 'PCM');
expect(CodecUtils.formatAudioCodec('audio/alac'), 'ALAC');
});
});
group('CodecUtils.formatAudioChannels', () {
@@ -50,6 +50,22 @@ class _PropertyPlayer implements Player {
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
/// Player fake that reports native (Android ExoPlayer) stats.
class _NativeStatsPlayer extends _PropertyPlayer {
_NativeStatsPlayer(this.stats) : super(const {});
final Map<String, dynamic> stats;
@override
bool get providesNativeStats => true;
@override
Future<Map<String, dynamic>> getStats() async => stats;
@override
Future<String> runtimePlayerType() async => 'exoplayer';
}
Future<PerformanceStats> _firstStats(_PropertyPlayer player) async {
final service = PerformanceStatsService(player);
try {
@@ -117,4 +133,51 @@ void main() {
expect(stats.audioPassthroughFormatted, 'DTS-HD');
});
});
group('PerformanceStatsService ExoPlayer codec display (#2063)', () {
test('falls back to the sample MIME type when the container has no codecs string', () async {
// Matroska via the FFmpeg demuxer: Format.codecs is null; only the
// MIME types identify the streams.
final stats = await _firstStats(
_NativeStatsPlayer({
'playerType': 'exoplayer',
'videoCodec': null,
'videoMimeType': 'video/hevc',
'audioCodec': null,
'audioMimeType': 'audio/eac3',
'audioSampleRate': 48000,
'audioChannels': 6,
}),
);
expect(stats.videoCodec, 'HEVC');
expect(stats.audioCodec, 'E-AC3');
expect(stats.audioChannels, '5.1');
});
test('an explicit codecs string wins over the MIME type', () async {
final stats = await _firstStats(
_NativeStatsPlayer({
'playerType': 'exoplayer',
'videoCodec': 'hvc1.2.4.L153.B0',
'videoMimeType': 'video/hevc',
'audioCodec': 'mp4a.40.2',
'audioMimeType': 'audio/mp4a-latm',
}),
);
expect(stats.videoCodec, 'HEVC');
expect(stats.audioCodec, 'AAC');
});
test('measured audio bitrate from the native side is surfaced', () async {
final stats = await _firstStats(
_NativeStatsPlayer({'playerType': 'exoplayer', 'audioMimeType': 'audio/vnd.dts', 'audioBitrate': 1509000}),
);
expect(stats.audioCodec, 'DTS');
expect(stats.hasValidAudioBitrate, isTrue);
expect(stats.audioBitrateFormatted, '1509 kbps');
});
});
}