From d7697176ed98787d6d67e0dbb994b26d95fcfb10 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Tue, 15 Sep 2015 13:45:54 +0100 Subject: [PATCH] Include language in audio formats. Issue: #437 --- .../exoplayer/demo/PlayerActivity.java | 33 ++++++++++++------- .../android/exoplayer/MediaFormatTest.java | 8 ++--- .../google/android/exoplayer/MediaFormat.java | 15 +++++---- .../exoplayer/dash/DashChunkSource.java | 6 ++-- .../exoplayer/extractor/mp3/Mp3Extractor.java | 2 +- .../exoplayer/extractor/mp4/AtomParsers.java | 19 ++++++----- .../exoplayer/extractor/ts/Ac3Reader.java | 2 +- .../exoplayer/extractor/ts/AdtsReader.java | 2 +- .../extractor/ts/MpegAudioReader.java | 2 +- .../exoplayer/extractor/ts/SeiReader.java | 2 +- .../extractor/webm/WebmExtractor.java | 4 +-- .../SmoothStreamingChunkSource.java | 7 ++-- .../android/exoplayer/util/Ac3Util.java | 24 +++++++++----- 13 files changed, 73 insertions(+), 53 deletions(-) diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java index d544b747b8..97b9050967 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java @@ -450,30 +450,41 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, private static String buildTrackName(MediaFormat format) { if (format.adaptive) { return "auto"; - } else if (MimeTypes.isVideo(format.mimeType)) { - return buildResolutionString(format) + buildBitrateString(format); - } else if (MimeTypes.isAudio(format.mimeType)) { - return buildAudioPropertyString(format) + buildBitrateString(format); - } else if (!TextUtils.isEmpty(format.language)) { - return format.language + buildBitrateString(format); - } else { - return "unknown" + buildBitrateString(format); } + String trackName; + if (MimeTypes.isVideo(format.mimeType)) { + trackName = joinWithSeparator(buildResolutionString(format), buildBitrateString(format)); + } else if (MimeTypes.isAudio(format.mimeType)) { + trackName = joinWithSeparator(joinWithSeparator(buildLanguageString(format), + buildAudioPropertyString(format)), buildBitrateString(format)); + } else { + trackName = joinWithSeparator(buildLanguageString(format), buildBitrateString(format)); + } + return trackName.length() == 0 ? "unknown" : trackName; } private static String buildResolutionString(MediaFormat format) { return format.width == MediaFormat.NO_VALUE || format.height == MediaFormat.NO_VALUE - ? "video" : format.width + "x" + format.height; + ? "" : format.width + "x" + format.height; } private static String buildAudioPropertyString(MediaFormat format) { return format.channelCount == MediaFormat.NO_VALUE || format.sampleRate == MediaFormat.NO_VALUE - ? "audio" : format.channelCount + "ch, " + format.sampleRate + "Hz"; + ? "" : format.channelCount + "ch, " + format.sampleRate + "Hz"; + } + + private static String buildLanguageString(MediaFormat format) { + return TextUtils.isEmpty(format.language) || "und".equals(format.language) ? "" + : format.language; } private static String buildBitrateString(MediaFormat format) { return format.bitrate == MediaFormat.NO_VALUE ? "" - : String.format(Locale.US, " (%.2fMbit)", format.bitrate / 1000000f); + : String.format(Locale.US, "%.2fMbit", format.bitrate / 1000000f); + } + + private static String joinWithSeparator(String first, String second) { + return first.length() == 0 ? second : (second.length() == 0 ? first : first + ", " + second); } private boolean onTrackItemClick(MenuItem item, int type) { diff --git a/library/src/androidTest/java/com/google/android/exoplayer/MediaFormatTest.java b/library/src/androidTest/java/com/google/android/exoplayer/MediaFormatTest.java index 5e265e3b12..dc47cf2ae4 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/MediaFormatTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/MediaFormatTest.java @@ -49,13 +49,13 @@ public final class MediaFormatTest extends TestCase { testConversionToFrameworkFormatV16(MediaFormat.createVideoFormat( "video/xyz", 5000, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, 1280, 720, null)); testConversionToFrameworkFormatV16(MediaFormat.createAudioFormat( - "audio/xyz", 500, 128, 1000L, 5, 44100, initData)); + "audio/xyz", 500, 128, 1000L, 5, 44100, initData, null)); testConversionToFrameworkFormatV16(MediaFormat.createAudioFormat( - "audio/xyz", 500, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, 5, 44100, null)); + "audio/xyz", 500, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, 5, 44100, null, null)); testConversionToFrameworkFormatV16( - MediaFormat.createTextFormat("text/xyz", MediaFormat.NO_VALUE, "eng", 1000L)); + MediaFormat.createTextFormat("text/xyz", MediaFormat.NO_VALUE, 1000L, "eng")); testConversionToFrameworkFormatV16( - MediaFormat.createTextFormat("text/xyz", MediaFormat.NO_VALUE, null, C.UNKNOWN_TIME_US)); + MediaFormat.createTextFormat("text/xyz", MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, null)); } @SuppressLint("InlinedApi") diff --git a/library/src/main/java/com/google/android/exoplayer/MediaFormat.java b/library/src/main/java/com/google/android/exoplayer/MediaFormat.java index 1cf4ca1d72..0f7f3fad1b 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaFormat.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaFormat.java @@ -149,19 +149,20 @@ public final class MediaFormat { } public static MediaFormat createAudioFormat(String mimeType, int bitrate, int maxInputSize, - long durationUs, int channelCount, int sampleRate, List initializationData) { + long durationUs, int channelCount, int sampleRate, List initializationData, + String language) { return new MediaFormat(mimeType, bitrate, maxInputSize, durationUs, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, channelCount, sampleRate, null, OFFSET_SAMPLE_RELATIVE, + NO_VALUE, NO_VALUE, channelCount, sampleRate, language, OFFSET_SAMPLE_RELATIVE, initializationData, false, NO_VALUE, NO_VALUE); } - public static MediaFormat createTextFormat(String mimeType, int bitrate, String language, - long durationUs) { - return createTextFormat(mimeType, bitrate, language, durationUs, OFFSET_SAMPLE_RELATIVE); + public static MediaFormat createTextFormat(String mimeType, int bitrate, long durationUs, + String language) { + return createTextFormat(mimeType, bitrate, durationUs, language, OFFSET_SAMPLE_RELATIVE); } - public static MediaFormat createTextFormat(String mimeType, int bitrate, String language, - long durationUs, long subsampleOffsetUs) { + public static MediaFormat createTextFormat(String mimeType, int bitrate, long durationUs, + String language, long subsampleOffsetUs) { return new MediaFormat(mimeType, bitrate, NO_VALUE, durationUs, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language, subsampleOffsetUs, null, false, NO_VALUE, NO_VALUE); } diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java index c70689685a..8a27410920 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java @@ -610,10 +610,10 @@ public class DashChunkSource implements ChunkSource, Output { durationUs, format.width, format.height, null); case AdaptationSet.TYPE_AUDIO: return MediaFormat.createAudioFormat(mediaMimeType, format.bitrate, MediaFormat.NO_VALUE, - durationUs, format.audioChannels, format.audioSamplingRate, null); + durationUs, format.audioChannels, format.audioSamplingRate, null, format.language); case AdaptationSet.TYPE_TEXT: - return MediaFormat.createTextFormat(mediaMimeType, format.bitrate, format.language, - durationUs); + return MediaFormat.createTextFormat(mediaMimeType, format.bitrate, durationUs, + format.language); default: return null; } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java index 6ca643dac2..34249c0069 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java @@ -315,7 +315,7 @@ public final class Mp3Extractor implements Extractor { extractorOutput.seekMap(seeker); trackOutput.format(MediaFormat.createAudioFormat(synchronizedHeader.mimeType, MediaFormat.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, seeker.getDurationUs(), - synchronizedHeader.channels, synchronizedHeader.sampleRate, null)); + synchronizedHeader.channels, synchronizedHeader.sampleRate, null, null)); } return headerPosition; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java index 732deca9b6..0a9d179bda 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java @@ -376,16 +376,16 @@ import java.util.List; || childAtomType == Atom.TYPE_dtsc || childAtomType == Atom.TYPE_dtse || childAtomType == Atom.TYPE_dtsh || childAtomType == Atom.TYPE_dtsl) { parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, durationUs, - out, i); + language, out, i); } else if (childAtomType == Atom.TYPE_TTML) { out.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TTML, - MediaFormat.NO_VALUE, language, durationUs); + MediaFormat.NO_VALUE, durationUs, language); } else if (childAtomType == Atom.TYPE_tx3g) { out.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TX3G, - MediaFormat.NO_VALUE, language, durationUs); + MediaFormat.NO_VALUE, durationUs, language); } else if (childAtomType == Atom.TYPE_stpp) { out.mediaFormat = MediaFormat.createTextFormat(MimeTypes.APPLICATION_TTML, - MediaFormat.NO_VALUE, language, durationUs, 0 /* subsample timing is absolute */); + MediaFormat.NO_VALUE, durationUs, language, 0 /* subsample timing is absolute */); } stsd.setPosition(childStartPosition + childAtomSize); } @@ -585,7 +585,7 @@ import java.util.List; } private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position, - int size, long durationUs, StsdData out, int entryIndex) { + int size, long durationUs, String language, StsdData out, int entryIndex) { parent.setPosition(position + Atom.HEADER_SIZE); parent.skipBytes(16); int channelCount = parent.readUnsignedShort(); @@ -635,17 +635,17 @@ import java.util.List; // TODO: Choose the right AC-3 track based on the contents of dac3/dec3. // TODO: Add support for encryption (by setting out.trackEncryptionBoxes). parent.setPosition(Atom.HEADER_SIZE + childStartPosition); - out.mediaFormat = Ac3Util.parseAnnexFAc3Format(parent, durationUs); + out.mediaFormat = Ac3Util.parseAnnexFAc3Format(parent, durationUs, language); return; } else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) { parent.setPosition(Atom.HEADER_SIZE + childStartPosition); - out.mediaFormat = Ac3Util.parseAnnexFEAc3Format(parent, durationUs); + out.mediaFormat = Ac3Util.parseAnnexFEAc3Format(parent, durationUs, language); return; } else if ((atomType == Atom.TYPE_dtsc || atomType == Atom.TYPE_dtse || atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl) && childAtomType == Atom.TYPE_ddts) { out.mediaFormat = MediaFormat.createAudioFormat(mimeType, MediaFormat.NO_VALUE, - MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null); + MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null, language); return; } childPosition += childAtomSize; @@ -658,7 +658,8 @@ import java.util.List; out.mediaFormat = MediaFormat.createAudioFormat(mimeType, MediaFormat.NO_VALUE, sampleSize, durationUs, channelCount, sampleRate, - initializationData == null ? null : Collections.singletonList(initializationData)); + initializationData == null ? null : Collections.singletonList(initializationData), + language); } /** Returns codec-specific initialization data contained in an esds box. */ diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/Ac3Reader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/Ac3Reader.java index 01bd707e4e..ba1afb30ac 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/Ac3Reader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/Ac3Reader.java @@ -155,7 +155,7 @@ import com.google.android.exoplayer.util.ParsableByteArray; sampleSize = Ac3Util.parseFrameSize(headerScratchBits); if (mediaFormat == null) { headerScratchBits.setPosition(0); - mediaFormat = Ac3Util.parseFrameAc3Format(headerScratchBits, C.UNKNOWN_TIME_US); + mediaFormat = Ac3Util.parseFrameAc3Format(headerScratchBits, C.UNKNOWN_TIME_US, null); output.format(mediaFormat); bitrate = Ac3Util.getBitrate(sampleSize, mediaFormat.sampleRate); } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java index d47fa5ce61..6f6e402bac 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java @@ -172,7 +172,7 @@ import java.util.Collections; MediaFormat mediaFormat = MediaFormat.createAudioFormat(MimeTypes.AUDIO_AAC, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, audioParams.second, - audioParams.first, Collections.singletonList(audioSpecificConfig)); + audioParams.first, Collections.singletonList(audioSpecificConfig), null); frameDurationUs = (C.MICROS_PER_SECOND * 1024L) / mediaFormat.sampleRate; output.format(mediaFormat); hasOutputFormat = true; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/MpegAudioReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/MpegAudioReader.java index edefe10af4..4498789803 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/MpegAudioReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/MpegAudioReader.java @@ -162,7 +162,7 @@ import com.google.android.exoplayer.util.ParsableByteArray; frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate; MediaFormat mediaFormat = MediaFormat.createAudioFormat(header.mimeType, MediaFormat.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, C.UNKNOWN_TIME_US, - header.channels, header.sampleRate, null); + header.channels, header.sampleRate, null, null); output.format(mediaFormat); hasOutputFormat = true; } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/SeiReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/SeiReader.java index 093bb4ade8..0389d8d1c4 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/SeiReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/SeiReader.java @@ -33,7 +33,7 @@ import com.google.android.exoplayer.util.ParsableByteArray; public SeiReader(TrackOutput output) { super(output); output.format(MediaFormat.createTextFormat(MimeTypes.APPLICATION_EIA608, MediaFormat.NO_VALUE, - null, C.UNKNOWN_TIME_US)); + C.UNKNOWN_TIME_US, null)); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java index f2af85fb6c..9d8f49e6b4 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java @@ -1210,12 +1210,12 @@ public final class WebmExtractor implements Extractor { MediaFormat format; if (MimeTypes.isAudio(mimeType)) { format = MediaFormat.createAudioFormat(mimeType, MediaFormat.NO_VALUE, maxInputSize, - durationUs, channelCount, sampleRate, initializationData); + durationUs, channelCount, sampleRate, initializationData, language); } else if (MimeTypes.isVideo(mimeType)) { format = MediaFormat.createVideoFormat(mimeType, MediaFormat.NO_VALUE, maxInputSize, durationUs, width, height, initializationData); } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) { - format = MediaFormat.createTextFormat(mimeType, MediaFormat.NO_VALUE, language, durationUs); + format = MediaFormat.createTextFormat(mimeType, MediaFormat.NO_VALUE, durationUs, language); } else { throw new ParserException("Unexpected MIME type."); } diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java index 65869cfcc2..c1d4edd7f8 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java @@ -411,12 +411,13 @@ public class SmoothStreamingChunkSource implements ChunkSource, format.audioSamplingRate, format.audioChannels)); } mediaFormat = MediaFormat.createAudioFormat(format.mimeType, format.bitrate, - MediaFormat.NO_VALUE, durationUs, format.audioChannels, format.audioSamplingRate, csd); + MediaFormat.NO_VALUE, durationUs, format.audioChannels, format.audioSamplingRate, csd, + format.language); mp4TrackType = Track.TYPE_soun; break; case StreamElement.TYPE_TEXT: - mediaFormat = MediaFormat.createTextFormat(format.mimeType, format.bitrate, format.language, - durationUs); + mediaFormat = MediaFormat.createTextFormat(format.mimeType, format.bitrate, durationUs, + format.language); mp4TrackType = Track.TYPE_text; break; default: diff --git a/library/src/main/java/com/google/android/exoplayer/util/Ac3Util.java b/library/src/main/java/com/google/android/exoplayer/util/Ac3Util.java index 66873f7213..1625033aaa 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/Ac3Util.java +++ b/library/src/main/java/com/google/android/exoplayer/util/Ac3Util.java @@ -39,9 +39,11 @@ public final class Ac3Util { * * @param data The AC3SpecificBox. * @param durationUs The duration to set on the format, in microseconds. - * @return The FAc3 format parsed from data in the header. + * @param language The language to set on the format. + * @return The AC-3 format parsed from data in the header. */ - public static MediaFormat parseAnnexFAc3Format(ParsableByteArray data, long durationUs) { + public static MediaFormat parseAnnexFAc3Format(ParsableByteArray data, long durationUs, + String language) { // fscod (sample rate code) int fscod = (data.readUnsignedByte() & 0xC0) >> 6; int sampleRate = SAMPLE_RATES[fscod]; @@ -53,18 +55,20 @@ public final class Ac3Util { channelCount++; } return MediaFormat.createAudioFormat(MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE, - MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null); + MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null, language); } /** - * Returns the AC-3 format given {@code data} containing the EC3SpecificBox according to + * Returns the E-AC-3 format given {@code data} containing the EC3SpecificBox according to * ETSI TS 102 366 Annex F. * * @param data The EC3SpecificBox. * @param durationUs The duration to set on the format, in microseconds. - * @return The FEAc3 format parsed from data in the header. + * @param language The language to set on the format. + * @return The E-AC-3 format parsed from data in the header. */ - public static MediaFormat parseAnnexFEAc3Format(ParsableByteArray data, long durationUs) { + public static MediaFormat parseAnnexFEAc3Format(ParsableByteArray data, long durationUs, + String language) { data.skipBytes(2); // Skip data_rate and num_ind_sub. // Read only the first substream. @@ -80,7 +84,7 @@ public final class Ac3Util { channelCount++; } return MediaFormat.createAudioFormat(MimeTypes.AUDIO_EC3, MediaFormat.NO_VALUE, - MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null); + MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null, language); } /** @@ -89,9 +93,11 @@ public final class Ac3Util { * * @param data Data to parse, positioned at the start of the syncword. * @param durationUs The duration to set on the format, in microseconds. + * @param language The language to set on the format. * @return The AC-3 format parsed from data in the header. */ - public static MediaFormat parseFrameAc3Format(ParsableBitArray data, long durationUs) { + public static MediaFormat parseFrameAc3Format(ParsableBitArray data, long durationUs, + String language) { // Skip syncword and crc1. data.skipBits(4 * 8); @@ -110,7 +116,7 @@ public final class Ac3Util { boolean lfeon = data.readBit(); return MediaFormat.createAudioFormat(MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, durationUs, CHANNEL_COUNTS[acmod] + (lfeon ? 1 : 0), - SAMPLE_RATES[fscod], null); + SAMPLE_RATES[fscod], null, language); } /**