public static String buildTrackName(Format format) { String trackName; if (MimeTypes.isVideo(format.sampleMimeType)) { trackName = joinWithSeparator( buildResolutionString(format), buildBitrateString(format)); } else if (MimeTypes.isAudio(format.sampleMimeType)) { trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(joinWithSeparator( buildLanguageString(format), buildAudioPropertyString(format)), buildBitrateString(format)), buildTrackIdString(format)), buildSampleMimeTypeString(format)); } else { trackName = joinWithSeparator(joinWithSeparator(joinWithSeparator(buildLanguageString(format), buildBitrateString(format)), buildTrackIdString(format)), buildSampleMimeTypeString(format)); } return trackName.length() == 0 ? "unknown" : trackName; }
public void testWidevineOfflineLicense() throws Exception { if (Util.SDK_INT < 22) { // Pass. return; } String streamName = "test_widevine_h264_fixed_offline"; DashHostedTestEncParameters parameters = newDashHostedTestEncParameters( WIDEVINE_H264_MANIFEST_PREFIX, true, MimeTypes.VIDEO_H264); TestOfflineLicenseHelper helper = new TestOfflineLicenseHelper(parameters); try { byte[] keySetId = helper.downloadLicense(); testDashPlayback(getActivity(), streamName, null, true, parameters, WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, false, keySetId, WIDEVINE_H264_CDD_FIXED); helper.renewLicense(); } finally { helper.releaseResources(); } }
public void testWidevineOfflineLicenseExpiresOnPause() throws Exception { if (Util.SDK_INT < 22) { // Pass. return; } String streamName = "test_widevine_h264_fixed_offline"; DashHostedTestEncParameters parameters = newDashHostedTestEncParameters( WIDEVINE_H264_MANIFEST_PREFIX, true, MimeTypes.VIDEO_H264); TestOfflineLicenseHelper helper = new TestOfflineLicenseHelper(parameters); try { byte[] keySetId = helper.downloadLicense(); // During playback pause until the license expires then continue playback Pair<Long, Long> licenseDurationRemainingSec = helper.getLicenseDurationRemainingSec(); long licenseDuration = licenseDurationRemainingSec.first; assertTrue("License duration should be less than 30 sec. " + "Server settings might have changed.", licenseDuration < 30); ActionSchedule schedule = new ActionSchedule.Builder(TAG) .delay(3000).pause().delay(licenseDuration * 1000 + 2000).play().build(); // DefaultDrmSessionManager should renew the license and stream play fine testDashPlayback(getActivity(), streamName, schedule, true, parameters, WIDEVINE_AAC_AUDIO_REPRESENTATION_ID, false, keySetId, WIDEVINE_H264_CDD_FIXED); } finally { helper.releaseResources(); } }
/** * Derives a track format corresponding to a given container format, by combining it with sample * level information obtained from the samples. * * @param containerFormat The container format for which the track format should be derived. * @param sampleFormat A sample format from which to obtain sample level information. * @return The derived track format. */ private static Format deriveFormat(Format containerFormat, Format sampleFormat) { if (containerFormat == null) { return sampleFormat; } String codecs = null; int sampleTrackType = MimeTypes.getTrackType(sampleFormat.sampleMimeType); if (sampleTrackType == C.TRACK_TYPE_AUDIO) { codecs = getAudioCodecs(containerFormat.codecs); } else if (sampleTrackType == C.TRACK_TYPE_VIDEO) { codecs = getVideoCodecs(containerFormat.codecs); } return sampleFormat.copyWithContainerInfo(containerFormat.id, codecs, containerFormat.bitrate, containerFormat.width, containerFormat.height, containerFormat.selectionFlags, containerFormat.language); }
private static String getCodecsOfType(String codecs, int trackType) { if (TextUtils.isEmpty(codecs)) { return null; } String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)"); StringBuilder builder = new StringBuilder(); for (String codec : codecArray) { if (trackType == MimeTypes.getTrackTypeOfCodec(codec)) { if (builder.length() > 0) { builder.append(","); } builder.append(codec); } } return builder.length() > 0 ? builder.toString() : null; }
@Override public void consume(ParsableByteArray sectionData) { if (!formatDeclared) { if (timestampAdjuster.getTimestampOffsetUs() == C.TIME_UNSET) { // There is not enough information to initialize the timestamp adjuster. return; } output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_SCTE35, timestampAdjuster.getTimestampOffsetUs())); formatDeclared = true; } int sampleSize = sectionData.bytesLeft(); output.sampleData(sectionData, sampleSize); output.sampleMetadata(timestampAdjuster.getLastAdjustedTimestampUs(), C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); }
@Override protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) throws IOException, InterruptedException { if (vorbisSetup != null) { return false; } vorbisSetup = readSetupHeaders(packet); if (vorbisSetup == null) { return true; } ArrayList<byte[]> codecInitialisationData = new ArrayList<>(); codecInitialisationData.add(vorbisSetup.idHeader.data); codecInitialisationData.add(vorbisSetup.setupHeaderData); setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_VORBIS, null, this.vorbisSetup.idHeader.bitrateNominal, Format.NO_VALUE, this.vorbisSetup.idHeader.channels, (int) this.vorbisSetup.idHeader.sampleRate, codecInitialisationData, null, 0, null); return true; }
@Override protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) throws IOException, InterruptedException { if (!headerRead) { byte[] metadata = Arrays.copyOf(packet.data, packet.limit()); int channelCount = metadata[9] & 0xFF; int preskip = ((metadata[11] & 0xFF) << 8) | (metadata[10] & 0xFF); List<byte[]> initializationData = new ArrayList<>(3); initializationData.add(metadata); putNativeOrderLong(initializationData, preskip); putNativeOrderLong(initializationData, DEFAULT_SEEK_PRE_ROLL_SAMPLES); setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_OPUS, null, Format.NO_VALUE, Format.NO_VALUE, channelCount, SAMPLE_RATE, initializationData, null, 0, null); headerRead = true; } else { boolean headerPacket = packet.readInt() == OPUS_CODE; packet.setPosition(0); return headerPacket; } return true; }
/** Returns DrmInitData from leaf atoms. */ private static DrmInitData getDrmInitDataFromAtoms(List<Atom.LeafAtom> leafChildren) { ArrayList<SchemeData> schemeDatas = null; int leafChildrenSize = leafChildren.size(); for (int i = 0; i < leafChildrenSize; i++) { LeafAtom child = leafChildren.get(i); if (child.type == Atom.TYPE_pssh) { if (schemeDatas == null) { schemeDatas = new ArrayList<>(); } byte[] psshData = child.data.data; UUID uuid = PsshAtomUtil.parseUuid(psshData); if (uuid == null) { Log.w(TAG, "Skipped pssh atom (failed to extract uuid)"); } else { schemeDatas.add(new SchemeData(uuid, MimeTypes.VIDEO_MP4, psshData)); } } } return schemeDatas == null ? null : new DrmInitData(schemeDatas); }
public Cea608Decoder(String mimeType, int accessibilityChannel) { ccData = new ParsableByteArray(); cueBuilders = new LinkedList<>(); currentCueBuilder = new CueBuilder(CC_MODE_UNKNOWN, DEFAULT_CAPTIONS_ROW_COUNT); packetLength = MimeTypes.APPLICATION_MP4CEA608.equals(mimeType) ? 2 : 3; switch (accessibilityChannel) { case 3: case 4: selectedField = 2; break; case 1: case 2: case Format.NO_VALUE: default: selectedField = 1; } setCaptionMode(CC_MODE_UNKNOWN); resetCueBuilders(); }
@Override public SubtitleDecoder createDecoder(Format format) { switch (format.sampleMimeType) { case MimeTypes.TEXT_VTT: return new WebvttDecoder(); case MimeTypes.APPLICATION_MP4VTT: return new Mp4WebvttDecoder(); case MimeTypes.APPLICATION_TTML: return new TtmlDecoder(); case MimeTypes.APPLICATION_SUBRIP: return new SubripDecoder(); case MimeTypes.APPLICATION_TX3G: return new Tx3gDecoder(format.initializationData); case MimeTypes.APPLICATION_CEA608: case MimeTypes.APPLICATION_MP4CEA608: return new Cea608Decoder(format.sampleMimeType, format.accessibilityChannel); case MimeTypes.APPLICATION_CEA708: return new Cea708Decoder(format.accessibilityChannel); case MimeTypes.APPLICATION_DVBSUBS: return new DvbDecoder(format.initializationData); default: throw new IllegalArgumentException("Attempted to create decoder for unsupported format"); } }
/** * Returns the maximum frame size supported by the default H264 decoder. * * @return The maximum frame size for an H264 stream that can be decoded on the device. */ public static int maxH264DecodableFrameSize() throws DecoderQueryException { if (maxH264DecodableFrameSize == -1) { int result = 0; MediaCodecInfo decoderInfo = getDecoderInfo(MimeTypes.VIDEO_H264, false); if (decoderInfo != null) { for (CodecProfileLevel profileLevel : decoderInfo.getProfileLevels()) { result = Math.max(avcLevelToMaxFrameSize(profileLevel.level), result); } // We assume support for at least 480p (SDK_INT >= 21) or 360p (SDK_INT < 21), which are // the levels mandated by the Android CDD. result = Math.max(result, Util.SDK_INT >= 21 ? (720 * 480) : (480 * 360)); } maxH264DecodableFrameSize = result; } return maxH264DecodableFrameSize; }
@Override protected int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format) throws DecoderQueryException { String mimeType = format.sampleMimeType; if (!MimeTypes.isAudio(mimeType)) { return FORMAT_UNSUPPORTED_TYPE; } int tunnelingSupport = Util.SDK_INT >= 21 ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED; if (allowPassthrough(mimeType) && mediaCodecSelector.getPassthroughDecoderInfo() != null) { return ADAPTIVE_NOT_SEAMLESS | tunnelingSupport | FORMAT_HANDLED; } MediaCodecInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType, false); if (decoderInfo == null) { return FORMAT_UNSUPPORTED_SUBTYPE; } // Note: We assume support for unknown sampleRate and channelCount. boolean decoderCapable = Util.SDK_INT < 21 || ((format.sampleRate == Format.NO_VALUE || decoderInfo.isAudioSampleRateSupportedV21(format.sampleRate)) && (format.channelCount == Format.NO_VALUE || decoderInfo.isAudioChannelCountSupportedV21(format.channelCount))); int formatSupport = decoderCapable ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES; return ADAPTIVE_NOT_SEAMLESS | tunnelingSupport | formatSupport; }
@Override protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) throws ExoPlaybackException { boolean passthrough = passthroughMediaFormat != null; String mimeType = passthrough ? passthroughMediaFormat.getString(MediaFormat.KEY_MIME) : MimeTypes.AUDIO_RAW; MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat; int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); int[] channelMap; if (codecNeedsDiscardChannelsWorkaround && channelCount == 6 && this.channelCount < 6) { channelMap = new int[this.channelCount]; for (int i = 0; i < this.channelCount; i++) { channelMap[i] = i; } } else { channelMap = null; } try { audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0, channelMap); } catch (AudioTrack.ConfigurationException e) { throw ExoPlaybackException.createForRenderer(e, getIndex()); } }
/** * Returns the E-AC-3 format given {@code data} containing the EC3SpecificBox according to * ETSI TS 102 366 Annex F. The reading position of {@code data} will be modified. * * @param data The EC3SpecificBox to parse. * @param trackId The track identifier to set on the format, or null. * @param language The language to set on the format. * @param drmInitData {@link DrmInitData} to be included in the format. * @return The E-AC-3 format parsed from data in the header. */ public static Format parseEAc3AnnexFFormat(ParsableByteArray data, String trackId, String language, DrmInitData drmInitData) { data.skipBytes(2); // data_rate, num_ind_sub // Read only the first substream. // TODO: Read later substreams? int fscod = (data.readUnsignedByte() & 0xC0) >> 6; int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; int nextByte = data.readUnsignedByte(); int channelCount = CHANNEL_COUNT_BY_ACMOD[(nextByte & 0x0E) >> 1]; if ((nextByte & 0x01) != 0) { // lfeon channelCount++; } return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, null, Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0, language); }
/** * Returns the DTS format given {@code data} containing the DTS frame according to ETSI TS 102 114 * subsections 5.3/5.4. * * @param frame The DTS frame to parse. * @param trackId The track identifier to set on the format, or null. * @param language The language to set on the format. * @param drmInitData {@link DrmInitData} to be included in the format. * @return The DTS format parsed from data in the header. */ public static Format parseDtsFormat(byte[] frame, String trackId, String language, DrmInitData drmInitData) { ParsableBitArray frameBits = new ParsableBitArray(frame); frameBits.skipBits(4 * 8 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE int amode = frameBits.readBits(6); int channelCount = CHANNELS_BY_AMODE[amode]; int sfreq = frameBits.readBits(4); int sampleRate = SAMPLE_RATE_BY_SFREQ[sfreq]; int rate = frameBits.readBits(5); int bitrate = rate >= TWICE_BITRATE_KBPS_BY_RATE.length ? Format.NO_VALUE : TWICE_BITRATE_KBPS_BY_RATE[rate] * 1000 / 2; frameBits.skipBits(10); // MIX, DYNF, TIMEF, AUXF, HDCD, EXT_AUDIO_ID, EXT_AUDIO, ASPF channelCount += frameBits.readBits(2) > 0 ? 1 : 0; // LFF return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_DTS, null, bitrate, Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0, language); }
@Override protected MediaSource[] doInBackground(MediaFile... media) { try { browser = Browser.getInstance(Config.mountDirectory); DataSource.Factory dataSourceFactory = new NfsDataSourceFactory(browser.getContext()); ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory(); MediaSource videoSource = new ExtractorMediaSource(Uri.parse("nfs://host/" + media[0].getPath()), dataSourceFactory, extractorsFactory, null, null); if (media[0].getSubtitlePath() != null) { Format subtitleFormat = Format.createTextSampleFormat(null, MimeTypes.APPLICATION_SUBRIP, null, Format.NO_VALUE, Format.NO_VALUE, "en", null); MediaSource subtitleSource = new SingleSampleMediaSource(Uri.parse("nfs://host/" + media[0].getSubtitlePath()), dataSourceFactory, subtitleFormat, Long.MAX_VALUE, 0); return new MediaSource[]{videoSource, subtitleSource}; } else return new MediaSource[]{videoSource}; } catch (IOException e) { e.printStackTrace(); } return null; }
@Override protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) throws IOException, InterruptedException { if (vorbisSetup != null) { return false; } vorbisSetup = readSetupHeaders(packet); if (vorbisSetup == null) { return true; } ArrayList<byte[]> codecInitialisationData = new ArrayList<>(); codecInitialisationData.add(vorbisSetup.idHeader.data); codecInitialisationData.add(vorbisSetup.setupHeaderData); setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_VORBIS, null, this.vorbisSetup.idHeader.bitrateNominal, OggPageHeader.MAX_PAGE_PAYLOAD, this.vorbisSetup.idHeader.channels, (int) this.vorbisSetup.idHeader.sampleRate, codecInitialisationData, null, 0, null); return true; }
@Override protected boolean parseHeader(ParsableByteArray data) throws UnsupportedFormatException { if (!hasParsedAudioDataHeader) { int header = data.readUnsignedByte(); audioFormat = (header >> 4) & 0x0F; // TODO: Add support for MP3. if (audioFormat == AUDIO_FORMAT_ALAW || audioFormat == AUDIO_FORMAT_ULAW) { String type = audioFormat == AUDIO_FORMAT_ALAW ? MimeTypes.AUDIO_ALAW : MimeTypes.AUDIO_ULAW; int pcmEncoding = (header & 0x01) == 1 ? C.ENCODING_PCM_16BIT : C.ENCODING_PCM_8BIT; Format format = Format.createAudioSampleFormat(null, type, null, Format.NO_VALUE, Format.NO_VALUE, 1, 8000, pcmEncoding, null, null, 0, null); output.format(format); hasOutputFormat = true; } else if (audioFormat != AUDIO_FORMAT_AAC) { throw new UnsupportedFormatException("Audio format not supported: " + audioFormat); } hasParsedAudioDataHeader = true; } else { // Skip header if it was parsed previously. data.skipBytes(1); } return true; }
@Override protected void parsePayload(ParsableByteArray data, long timeUs) { int packetType = data.readUnsignedByte(); if (packetType == AAC_PACKET_TYPE_SEQUENCE_HEADER && !hasOutputFormat) { // Parse the sequence header. byte[] audioSpecificConfig = new byte[data.bytesLeft()]; data.readBytes(audioSpecificConfig, 0, audioSpecificConfig.length); Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig( audioSpecificConfig); Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first, Collections.singletonList(audioSpecificConfig), null, 0, null); output.format(format); hasOutputFormat = true; } else if (audioFormat != AUDIO_FORMAT_AAC || packetType == AAC_PACKET_TYPE_AAC_RAW) { int sampleSize = data.bytesLeft(); output.sampleData(data, sampleSize); output.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); } }
@Override public SubtitleDecoder createDecoder(Format format) { try { Class<?> clazz = getDecoderClass(format.sampleMimeType); if (clazz == null) { throw new IllegalArgumentException("Attempted to create decoder for unsupported format"); } if (format.sampleMimeType.equals(MimeTypes.APPLICATION_CEA608) || format.sampleMimeType.equals(MimeTypes.APPLICATION_MP4CEA608)) { return clazz.asSubclass(SubtitleDecoder.class).getConstructor(String.class, Integer.TYPE) .newInstance(format.sampleMimeType, format.accessibilityChannel); } else if (format.sampleMimeType.equals(MimeTypes.APPLICATION_CEA708)) { return clazz.asSubclass(SubtitleDecoder.class).getConstructor(Integer.TYPE) .newInstance(format.accessibilityChannel); } else { return clazz.asSubclass(SubtitleDecoder.class).getConstructor().newInstance(); } } catch (Exception e) { throw new IllegalStateException("Unexpected error instantiating decoder", e); } }
private Class<?> getDecoderClass(String mimeType) { if (mimeType == null) { return null; } try { switch (mimeType) { case MimeTypes.APPLICATION_ID3: return Class.forName("com.google.android.exoplayer2.metadata.id3.Id3Decoder"); case MimeTypes.APPLICATION_EMSG: return Class.forName("com.google.android.exoplayer2.metadata.emsg.EventMessageDecoder"); case MimeTypes.APPLICATION_SCTE35: return Class.forName("com.google.android.exoplayer2.metadata.scte35.SpliceInfoDecoder"); default: return null; } } catch (ClassNotFoundException e) { return null; } }
/** * Derives a sample mimeType from a container mimeType and codecs attribute. * * @param containerMimeType The mimeType of the container. * @param codecs The codecs attribute. * @return The derived sample mimeType, or null if it could not be derived. */ private static String getSampleMimeType(String containerMimeType, String codecs) { if (MimeTypes.isAudio(containerMimeType)) { return MimeTypes.getAudioMediaMimeType(codecs); } else if (MimeTypes.isVideo(containerMimeType)) { return MimeTypes.getVideoMediaMimeType(codecs); } else if (mimeTypeIsRawText(containerMimeType)) { return containerMimeType; } else if (MimeTypes.APPLICATION_MP4.equals(containerMimeType)) { if ("stpp".equals(codecs)) { return MimeTypes.APPLICATION_TTML; } else if ("wvtt".equals(codecs)) { return MimeTypes.APPLICATION_MP4VTT; } } else if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) { if (codecs != null) { if (codecs.contains("cea708")) { return MimeTypes.APPLICATION_CEA708; } else if (codecs.contains("eia608") || codecs.contains("cea608")) { return MimeTypes.APPLICATION_CEA608; } } return null; } return null; }
@Override public Object build() { StreamElement[] streamElementArray = new StreamElement[streamElements.size()]; streamElements.toArray(streamElementArray); if (protectionElement != null) { DrmInitData drmInitData = new DrmInitData(new SchemeData(protectionElement.uuid, MimeTypes.VIDEO_MP4, protectionElement.data)); for (StreamElement streamElement : streamElementArray) { for (int i = 0; i < streamElement.formats.length; i++) { streamElement.formats[i] = streamElement.formats[i].copyWithDrmInitData(drmInitData); } } } return new SsManifest(majorVersion, minorVersion, timescale, duration, dvrWindowLength, lookAheadCount, isLive, protectionElement, streamElementArray); }
private static String fourCCToMimeType(String fourCC) { if (fourCC.equalsIgnoreCase("H264") || fourCC.equalsIgnoreCase("X264") || fourCC.equalsIgnoreCase("AVC1") || fourCC.equalsIgnoreCase("DAVC")) { return MimeTypes.VIDEO_H264; } else if (fourCC.equalsIgnoreCase("AAC") || fourCC.equalsIgnoreCase("AACL") || fourCC.equalsIgnoreCase("AACH") || fourCC.equalsIgnoreCase("AACP")) { return MimeTypes.AUDIO_AAC; } else if (fourCC.equalsIgnoreCase("TTML")) { return MimeTypes.APPLICATION_TTML; } else if (fourCC.equalsIgnoreCase("ac-3") || fourCC.equalsIgnoreCase("dac3")) { return MimeTypes.AUDIO_AC3; } else if (fourCC.equalsIgnoreCase("ec-3") || fourCC.equalsIgnoreCase("dec3")) { return MimeTypes.AUDIO_E_AC3; } else if (fourCC.equalsIgnoreCase("dtsc")) { return MimeTypes.AUDIO_DTS; } else if (fourCC.equalsIgnoreCase("dtsh") || fourCC.equalsIgnoreCase("dtsl")) { return MimeTypes.AUDIO_DTS_HD; } else if (fourCC.equalsIgnoreCase("dtse")) { return MimeTypes.AUDIO_DTS_EXPRESS; } else if (fourCC.equalsIgnoreCase("opus")) { return MimeTypes.AUDIO_OPUS; } return null; }
/** * Returns the AC-3 format given {@code data} containing a syncframe. The reading position of * {@code data} will be modified. * * @param data The data to parse, positioned at the start of the syncframe. * @param trackId The track identifier to set on the format, or null. * @param language The language to set on the format. * @param drmInitData {@link DrmInitData} to be included in the format. * @return The AC-3 format parsed from data in the header. */ public static Format parseAc3SyncframeFormat(ParsableBitArray data, String trackId, String language, DrmInitData drmInitData) { data.skipBits(16 + 16); // syncword, crc1 int fscod = data.readBits(2); data.skipBits(6 + 5 + 3); // frmsizecod, bsid, bsmod int acmod = data.readBits(3); if ((acmod & 0x01) != 0 && acmod != 1) { data.skipBits(2); // cmixlev } if ((acmod & 0x04) != 0) { data.skipBits(2); // surmixlev } if (acmod == 2) { data.skipBits(2); // dsurmod } boolean lfeon = data.readBit(); return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, null, Format.NO_VALUE, Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), SAMPLE_RATE_BY_FSCOD[fscod], null, drmInitData, 0, language); }
/** * Returns the E-AC-3 format given {@code data} containing a syncframe. The reading position of * {@code data} will be modified. * * @param data The data to parse, positioned at the start of the syncframe. * @param trackId The track identifier to set on the format, or null. * @param language The language to set on the format. * @param drmInitData {@link DrmInitData} to be included in the format. * @return The E-AC-3 format parsed from data in the header. */ public static Format parseEac3SyncframeFormat(ParsableBitArray data, String trackId, String language, DrmInitData drmInitData) { data.skipBits(16 + 2 + 3 + 11); // syncword, strmtype, substreamid, frmsiz int sampleRate; int fscod = data.readBits(2); if (fscod == 3) { sampleRate = SAMPLE_RATE_BY_FSCOD2[data.readBits(2)]; } else { data.skipBits(2); // numblkscod sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; } int acmod = data.readBits(3); boolean lfeon = data.readBit(); return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, null, Format.NO_VALUE, Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), sampleRate, null, drmInitData, 0, language); }
@Override protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) throws IOException, InterruptedException { if (!headerRead) { byte[] metadata = Arrays.copyOf(packet.data, packet.limit()); int channelCount = metadata[9] & 0xFF; int preskip = ((metadata[11] & 0xFF) << 8) | (metadata[10] & 0xFF); List<byte[]> initializationData = new ArrayList<>(3); initializationData.add(metadata); putNativeOrderLong(initializationData, preskip); putNativeOrderLong(initializationData, DEFAULT_SEEK_PRE_ROLL_SAMPLES); setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_OPUS, null, Format.NO_VALUE, Format.NO_VALUE, channelCount, SAMPLE_RATE, initializationData, null, 0, "und"); headerRead = true; } else { boolean headerPacket = packet.readInt() == OPUS_CODE; packet.setPosition(0); return headerPacket; } return true; }