Skip to content

Commit

Permalink
Add AC-4 format support
Browse files Browse the repository at this point in the history
 * Add AC-4 MIME type definition
 * Add AC-4 format support in Mp4Extractor and TsExtractor
 * Add AC-4 Extractor
 * Add AC-4 playback support in MPEG-4, MPEG-DASH, TS and HLS
  • Loading branch information
ybai001 committed Dec 26, 2018
1 parent 246d464 commit ae40208
Show file tree
Hide file tree
Showing 15 changed files with 729 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ private C() {}
* {@link #ENCODING_INVALID}, {@link #ENCODING_PCM_8BIT}, {@link #ENCODING_PCM_16BIT}, {@link
* #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link
* #ENCODING_PCM_MU_LAW}, {@link #ENCODING_PCM_A_LAW}, {@link #ENCODING_AC3}, {@link
* #ENCODING_E_AC3}, {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD} or {@link
* #ENCODING_DOLBY_TRUEHD}.
* #ENCODING_E_AC3}, {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD}, {@link
* #ENCODING_DOLBY_TRUEHD} or {@link #ENCODING_AC4}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
Expand All @@ -162,7 +162,8 @@ private C() {}
ENCODING_E_AC3,
ENCODING_DTS,
ENCODING_DTS_HD,
ENCODING_DOLBY_TRUEHD
ENCODING_DOLBY_TRUEHD,
ENCODING_AC4
})
public @interface Encoding {}

Expand Down Expand Up @@ -212,6 +213,8 @@ private C() {}
public static final int ENCODING_DTS_HD = AudioFormat.ENCODING_DTS_HD;
/** @see AudioFormat#ENCODING_DOLBY_TRUEHD */
public static final int ENCODING_DOLBY_TRUEHD = AudioFormat.ENCODING_DOLBY_TRUEHD;
/** @see AudioFormat#ENCODING_AC4 */
public static final int ENCODING_AC4 = AudioFormat.ENCODING_AC4;

/**
* Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.audio;

import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.nio.ByteBuffer;

/**
* Utility methods for parsing AC-4 frames, which are access units in AC-4 bitstreams.
*/
public final class Ac4Util {

/**
* Holds sample format information as presented by a syncframe header.
*/
public static final class SyncFrameInfo {

/**
* The sample mime type of the bitstream is {@link MimeTypes#AUDIO_AC4}.
*/
public final String mimeType;
/**
* The bitstream version.
*/
public final int bitstreamVersion;
/**
* The audio sampling rate in Hz.
*/
public final int sampleRate;
/**
* The number of audio channels
*/
public final int channelCount;
/**
* The size of the frame.
*/
public final int frameSize;
/**
* Number of audio samples in the frame.
*/
public final int sampleCount;

private SyncFrameInfo(
String mimeType,
int bitstreamVersion,
int channelCount,
int sampleRate,
int frameSize,
int sampleCount) {
this.mimeType = mimeType;
this.bitstreamVersion = bitstreamVersion;
this.channelCount = channelCount;
this.sampleRate = sampleRate;
this.frameSize = frameSize;
this.sampleCount = sampleCount;
}
}

/**
* The channel count of AC-4 stream.
*/
// TODO: Parse AC-4 stream channel count.
public static final int CHANNEL_COUNT_2 = 2;
/**
* The header size for AC-4 parser. Only needs to be as big as we need to read, not the full
* header size.
*/
public static final int HEADER_SIZE_FOR_PARSER = 16;
/**
* Number of audio samples in the frame. Defined in IEC61937-14:2017 table 5 and 6. This table
* provides the number of samples per frame at the playback sampling frequency of 48kHz. For
* 44.1kHz, only frame_rate_index(13) is valid and corresponding sample count is 2048.
*/
private static final int[] SAMPLE_COUNT = new int[] {
/* [ 0] 23.976 fps */ 2002,
/* [ 1] 24 fps */ 2000,
/* [ 2] 25 fps */ 1920,
/* [ 3] 29.97 fps */ 1601, // 1601 | 1602 | 1601 | 1602 | 1602
/* [ 4] 30 fps */ 1600,
/* [ 5] 47.95 fps */ 1001,
/* [ 6] 48 fps */ 1000,
/* [ 7] 50 fps */ 960,
/* [ 8] 59.94 fps */ 800, // 800 | 801 | 801 | 801 | 801
/* [ 9] 60 fps */ 800,
/* [10] 100 fps */ 480,
/* [11] 119.88 fps */ 400, // 400 | 400 | 401 | 400 | 401
/* [12] 120 fps */ 400,
/* [13] 23.438 fps */ 2048
};

/**
* Returns the AC-4 format given {@code data} containing the AC4SpecificBox according to ETSI TS
* 103 190-1 Annex E. The reading position of {@code data} will be modified.
*
* @param data The AC4SpecificBox to parse.
* @param trackId The track identifier to set on the format.
* @param language The language to set on the format.
* @param drmInitData {@link DrmInitData} to be included in the format.
* @return The AC-4 format parsed from data in the header.
*/
public static Format parseAc4AnnexEFormat(
ParsableByteArray data, String trackId, String language, DrmInitData drmInitData) {
data.skipBytes(1);
int sampleRate = ((data.readUnsignedByte() & 0x20) >> 5 == 1) ? 48000 : 44100;
return Format.createAudioSampleFormat(
trackId,
MimeTypes.AUDIO_AC4,
null,
Format.NO_VALUE,
Format.NO_VALUE,
CHANNEL_COUNT_2,
sampleRate,
null,
drmInitData,
0,
language);
}

private static int readVariableBits(ParsableBitArray data, int nbits) {
int value = 0;
while (true) {
int moreBits;
value += data.readBits(nbits);
moreBits = data.readBits(1);
if (moreBits == 0)
break;
value++;
value <<= nbits;
}
return value;
}

/**
* Returns AC-4 format information 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.
* @return The AC-4 format data parsed from the header.
*/
public static SyncFrameInfo parseAc4SyncframeInfo(ParsableBitArray data) {
int headerSize = 0;
int syncWord = data.readBits(16);
headerSize += 2;
int frameSize = data.readBits(16);
headerSize += 2;
if (frameSize == 0xFFFF) {
frameSize = data.readBits(24);
headerSize += 3;
}
frameSize += headerSize;
if (syncWord == 0xAC41) {
frameSize += 2;
}
int bitstreamVersion = data.readBits(2);
if (bitstreamVersion == 3){
bitstreamVersion += readVariableBits(data, 2);
}
int sequenceCounter = data.readBits(10);
if (data.readBits(1) == 1) {
if (data.readBits(3) > 0){
data.skipBits(2);
}
}
int sampleRate = (data.readBits(1) == 1) ? 48000 : 44100;
int frameRateIndex = data.readBits(4);
int sampleCount = 0;
if (sampleRate == 44100 && frameRateIndex == 13) {
sampleCount = SAMPLE_COUNT[frameRateIndex];
} else if (sampleRate == 48000 && frameRateIndex < SAMPLE_COUNT.length) {
sampleCount = SAMPLE_COUNT[frameRateIndex];
switch (sequenceCounter % 5) {
case 1:
case 3:
if (frameRateIndex == 3 || frameRateIndex == 8) {
sampleCount++;
}
break;
case 2:
if (frameRateIndex == 8 || frameRateIndex == 11) {
sampleCount++;
}
break;
case 4:
if (frameRateIndex == 3 || frameRateIndex == 8 || frameRateIndex == 11) {
sampleCount++;
}
break;
default:
break;
}
}
return new SyncFrameInfo(
MimeTypes.AUDIO_AC4, bitstreamVersion, CHANNEL_COUNT_2, sampleRate, frameSize, sampleCount);
}

/**
* Returns the size in bytes of the given AC-4 syncframe.
*
* @param data The syncframe to parse.
* @return The syncframe size in bytes. {@link C#LENGTH_UNSET} if the input is invalid.
*/
public static int parseAc4SyncframeSize(byte[] data, int syncBytes) {
if (data.length < 7) {
return C.LENGTH_UNSET;
}
int headerSize = 2; // syncword
int frameSize = ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
headerSize += 2;
if (frameSize == 0xFFFF) {
frameSize = ((data[4] & 0xFF) << 16) | ((data[5] & 0xFF) << 8) | (data[6] & 0xFF);
headerSize += 3;
}
if (syncBytes == 0xAC41) {
headerSize += 2;
}
frameSize += headerSize;
return frameSize;
}

/**
* Reads the number of audio samples represented by the given AC-4 syncframe. The buffer's
* position is not modified.
*
* @param buffer The {@link ByteBuffer} from which to read the syncframe.
* @return The number of audio samples represented by the syncframe.
*/
public static int parseAc4SyncframeAudioSampleCount(ByteBuffer buffer) {
byte[] bufferBytes = new byte[HEADER_SIZE_FOR_PARSER];
buffer.get(bufferBytes);
ParsableBitArray data = new ParsableBitArray(bufferBytes);
SyncFrameInfo ac4SyncframeInfo = parseAc4SyncframeInfo(data);
return ac4SyncframeInfo.sampleCount;
}

/**
* Create AC-4 sample header, which includes AC-4 syncword and frame size.
*
* @param sampleSize The size of AC-4 sample.
* @return The AC-4 sample header byte array.
*/
public static ParsableByteArray getAc4SampleHeader(int sampleSize) {
// Add AC-4 Syncword 0xAC40 and frame size to create AC-4 syncframe
// ETSI TS 103 190-1 V1.3.1, Annex G
byte[] ac4SampleHeader = new byte[] {(byte)0xAC, 0x40, (byte)0xFF, (byte)0xFF,
(byte)(sampleSize >> 16 & 0xFF), (byte)(sampleSize >> 8 & 0xFF), (byte)(sampleSize & 0xFF)};
return new ParsableByteArray(ac4SampleHeader);
}

private Ac4Util() {}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,8 @@ private static int getMaximumEncodedRateBytesPerSecond(@C.Encoding int encoding)
return 18000 * 1000 / 8;
case C.ENCODING_DOLBY_TRUEHD:
return 24500 * 1000 / 8;
case C.ENCODING_AC4:
return 2688 * 1000 / 8;
case C.ENCODING_INVALID:
case C.ENCODING_PCM_16BIT:
case C.ENCODING_PCM_24BIT:
Expand Down Expand Up @@ -1229,6 +1231,8 @@ private static int getFramesPerEncodedSample(@C.Encoding int encoding, ByteBuffe
? 0
: (Ac3Util.parseTrueHdSyncframeAudioSampleCount(buffer, syncframeOffset)
* Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT);
} else if (encoding == C.ENCODING_AC4) {
return Ac4Util.parseAc4SyncframeAudioSampleCount(buffer);
} else {
throw new IllegalStateException("Unexpected audio encoding: " + encoding);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,11 @@ protected MediaFormat getMediaFormat(
mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate);
}
}
if (MimeTypes.AUDIO_AC4.equals(format.sampleMimeType)) {
// Some devices handle AC-4 raw frame by default.
// Need to notify the codec to handle AC-4 sync frame.
mediaFormat.setInteger("ac4-is-sync", 1);
}
return mediaFormat;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
import com.google.android.exoplayer2.extractor.ogg.OggExtractor;
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
import com.google.android.exoplayer2.extractor.ts.Ac4Extractor;
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
import com.google.android.exoplayer2.extractor.ts.PsExtractor;
Expand Down Expand Up @@ -206,7 +207,7 @@ public synchronized DefaultExtractorsFactory setTsExtractorFlags(

@Override
public synchronized Extractor[] createExtractors() {
Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 12 : 13];
Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 13 : 14];
extractors[0] = new MatroskaExtractor(matroskaFlags);
extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags);
extractors[2] = new Mp4Extractor(mp4Flags);
Expand Down Expand Up @@ -235,9 +236,10 @@ public synchronized Extractor[] createExtractors() {
| (constantBitrateSeekingEnabled
? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
: 0));
extractors[12] = new Ac4Extractor();
if (FLAC_EXTRACTOR_CONSTRUCTOR != null) {
try {
extractors[12] = FLAC_EXTRACTOR_CONSTRUCTOR.newInstance();
extractors[13] = FLAC_EXTRACTOR_CONSTRUCTOR.newInstance();
} catch (Exception e) {
// Should never happen.
throw new IllegalStateException("Unexpected error creating FLAC extractor", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@
public static final int TYPE_dac3 = Util.getIntegerCodeForString("dac3");
public static final int TYPE_ec_3 = Util.getIntegerCodeForString("ec-3");
public static final int TYPE_dec3 = Util.getIntegerCodeForString("dec3");
public static final int TYPE_ac_4 = Util.getIntegerCodeForString("ac-4");
public static final int TYPE_dac4 = Util.getIntegerCodeForString("dac4");
public static final int TYPE_dtsc = Util.getIntegerCodeForString("dtsc");
public static final int TYPE_dtsh = Util.getIntegerCodeForString("dtsh");
public static final int TYPE_dtsl = Util.getIntegerCodeForString("dtsl");
Expand Down
Loading

0 comments on commit ae40208

Please sign in to comment.