mirror of
https://github.com/androidx/media.git
synced 2025-05-17 12:39:52 +08:00
Add AC-4 format support
* 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
This commit is contained in:
parent
ae40208d92
commit
460f576142
@ -49,6 +49,7 @@ import java.lang.reflect.Constructor;
|
|||||||
* <li>WAV ({@link WavExtractor})
|
* <li>WAV ({@link WavExtractor})
|
||||||
* <li>AC3 ({@link Ac3Extractor})
|
* <li>AC3 ({@link Ac3Extractor})
|
||||||
* <li>AMR ({@link AmrExtractor})
|
* <li>AMR ({@link AmrExtractor})
|
||||||
|
* <li>AC4 ({@link Ac4Extractor})
|
||||||
* <li>FLAC (only available if the FLAC extension is built and included)
|
* <li>FLAC (only available if the FLAC extension is built and included)
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
|
@ -161,6 +161,8 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
private int sampleBytesWritten;
|
private int sampleBytesWritten;
|
||||||
private int sampleCurrentNalBytesRemaining;
|
private int sampleCurrentNalBytesRemaining;
|
||||||
private boolean processSeiNalUnitPayload;
|
private boolean processSeiNalUnitPayload;
|
||||||
|
private boolean isAc4HeaderAdded;
|
||||||
|
private int ac4SampleHeaderSize;
|
||||||
|
|
||||||
// Extractor output.
|
// Extractor output.
|
||||||
private ExtractorOutput extractorOutput;
|
private ExtractorOutput extractorOutput;
|
||||||
@ -262,6 +264,8 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
durationUs = C.TIME_UNSET;
|
durationUs = C.TIME_UNSET;
|
||||||
pendingSeekTimeUs = C.TIME_UNSET;
|
pendingSeekTimeUs = C.TIME_UNSET;
|
||||||
segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET;
|
segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET;
|
||||||
|
isAc4HeaderAdded = false;
|
||||||
|
ac4SampleHeaderSize = 0;
|
||||||
enterReadingAtomHeaderState();
|
enterReadingAtomHeaderState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1217,6 +1221,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
sampleSize += sampleBytesWritten;
|
sampleSize += sampleBytesWritten;
|
||||||
parserState = STATE_READING_SAMPLE_CONTINUE;
|
parserState = STATE_READING_SAMPLE_CONTINUE;
|
||||||
sampleCurrentNalBytesRemaining = 0;
|
sampleCurrentNalBytesRemaining = 0;
|
||||||
|
isAc4HeaderAdded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackFragment fragment = currentTrackBundle.fragment;
|
TrackFragment fragment = currentTrackBundle.fragment;
|
||||||
@ -1277,17 +1282,20 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int sampleHeaderSize = 0;
|
if (MimeTypes.AUDIO_AC4.equals(track.format.sampleMimeType) && !isAc4HeaderAdded) {
|
||||||
if (MimeTypes.AUDIO_AC4.equals(track.format.sampleMimeType)) {
|
|
||||||
ParsableByteArray ac4SampleHeaderData = Ac4Util.getAc4SampleHeader(sampleSize);
|
ParsableByteArray ac4SampleHeaderData = Ac4Util.getAc4SampleHeader(sampleSize);
|
||||||
output.sampleData(ac4SampleHeaderData, ac4SampleHeaderData.capacity());
|
output.sampleData(ac4SampleHeaderData, ac4SampleHeaderData.capacity());
|
||||||
sampleHeaderSize = ac4SampleHeaderData.capacity();
|
ac4SampleHeaderSize = ac4SampleHeaderData.capacity();
|
||||||
|
isAc4HeaderAdded = true;
|
||||||
}
|
}
|
||||||
while (sampleBytesWritten < sampleSize) {
|
while (sampleBytesWritten < sampleSize) {
|
||||||
int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false);
|
int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false);
|
||||||
sampleBytesWritten += writtenBytes;
|
sampleBytesWritten += writtenBytes;
|
||||||
}
|
}
|
||||||
sampleSize += sampleHeaderSize;
|
if (MimeTypes.AUDIO_AC4.equals(track.format.sampleMimeType)) {
|
||||||
|
sampleSize += ac4SampleHeaderSize;
|
||||||
|
isAc4HeaderAdded = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@C.BufferFlags int sampleFlags = fragment.sampleIsSyncFrameTable[sampleIndex]
|
@C.BufferFlags int sampleFlags = fragment.sampleIsSyncFrameTable[sampleIndex]
|
||||||
|
@ -118,6 +118,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||||||
private int firstVideoTrackIndex;
|
private int firstVideoTrackIndex;
|
||||||
private long durationUs;
|
private long durationUs;
|
||||||
private boolean isQuickTime;
|
private boolean isQuickTime;
|
||||||
|
private boolean isAc4HeaderAdded;
|
||||||
|
private int ac4SampleHeaderSize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new extractor for unfragmented MP4 streams.
|
* Creates a new extractor for unfragmented MP4 streams.
|
||||||
@ -139,6 +141,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||||||
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
|
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
|
||||||
nalLength = new ParsableByteArray(4);
|
nalLength = new ParsableByteArray(4);
|
||||||
sampleTrackIndex = C.INDEX_UNSET;
|
sampleTrackIndex = C.INDEX_UNSET;
|
||||||
|
isAc4HeaderAdded = false;
|
||||||
|
ac4SampleHeaderSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -489,6 +493,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||||||
if (sampleTrackIndex == C.INDEX_UNSET) {
|
if (sampleTrackIndex == C.INDEX_UNSET) {
|
||||||
return RESULT_END_OF_INPUT;
|
return RESULT_END_OF_INPUT;
|
||||||
}
|
}
|
||||||
|
isAc4HeaderAdded = false;
|
||||||
}
|
}
|
||||||
Mp4Track track = tracks[sampleTrackIndex];
|
Mp4Track track = tracks[sampleTrackIndex];
|
||||||
TrackOutput trackOutput = track.trackOutput;
|
TrackOutput trackOutput = track.trackOutput;
|
||||||
@ -538,18 +543,21 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int sampleHeaderSize = 0;
|
if (MimeTypes.AUDIO_AC4.equals(track.track.format.sampleMimeType) && !isAc4HeaderAdded) {
|
||||||
if (MimeTypes.AUDIO_AC4.equals(track.track.format.sampleMimeType)) {
|
|
||||||
ParsableByteArray ac4SampleHeaderData = Ac4Util.getAc4SampleHeader(sampleSize);
|
ParsableByteArray ac4SampleHeaderData = Ac4Util.getAc4SampleHeader(sampleSize);
|
||||||
trackOutput.sampleData(ac4SampleHeaderData, ac4SampleHeaderData.capacity());
|
trackOutput.sampleData(ac4SampleHeaderData, ac4SampleHeaderData.capacity());
|
||||||
sampleHeaderSize = ac4SampleHeaderData.capacity();
|
ac4SampleHeaderSize = ac4SampleHeaderData.capacity();
|
||||||
|
isAc4HeaderAdded = true;
|
||||||
}
|
}
|
||||||
while (sampleBytesWritten < sampleSize) {
|
while (sampleBytesWritten < sampleSize) {
|
||||||
int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false);
|
int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false);
|
||||||
sampleBytesWritten += writtenBytes;
|
sampleBytesWritten += writtenBytes;
|
||||||
sampleCurrentNalBytesRemaining -= writtenBytes;
|
sampleCurrentNalBytesRemaining -= writtenBytes;
|
||||||
}
|
}
|
||||||
sampleSize += sampleHeaderSize;
|
if (MimeTypes.AUDIO_AC4.equals(track.track.format.sampleMimeType)) {
|
||||||
|
sampleSize += ac4SampleHeaderSize;
|
||||||
|
isAc4HeaderAdded = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
trackOutput.sampleMetadata(track.sampleTable.timestampsUs[sampleIndex],
|
trackOutput.sampleMetadata(track.sampleTable.timestampsUs[sampleIndex],
|
||||||
track.sampleTable.flags[sampleIndex], sampleSize, 0, null);
|
track.sampleTable.flags[sampleIndex], sampleSize, 0, null);
|
||||||
|
@ -115,9 +115,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
|
|||||||
if (skipToNextSync(data)) {
|
if (skipToNextSync(data)) {
|
||||||
state = STATE_READING_HEADER;
|
state = STATE_READING_HEADER;
|
||||||
headerScratchBytes.data[0] = (byte)0xAC;
|
headerScratchBytes.data[0] = (byte)0xAC;
|
||||||
headerScratchBytes.data[1] = 0x40;
|
headerScratchBytes.data[1] = headerScratchBytes.data[1] = (byte)(hasCRC ? 0x41 : 0x40);
|
||||||
if (hasCRC)
|
|
||||||
headerScratchBytes.data[1] = 0x41;
|
|
||||||
bytesRead = 2;
|
bytesRead = 2;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
BIN
library/core/src/test/assets/ts/sample.ac4
Normal file
BIN
library/core/src/test/assets/ts/sample.ac4
Normal file
Binary file not shown.
106
library/core/src/test/assets/ts/sample.ac4.0.dump
Normal file
106
library/core/src/test/assets/ts/sample.ac4.0.dump
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
seekMap:
|
||||||
|
isSeekable = false
|
||||||
|
duration = UNSET TIME
|
||||||
|
getPosition(0) = [[timeUs=0, position=0]]
|
||||||
|
numberOfTracks = 1
|
||||||
|
track 0:
|
||||||
|
format:
|
||||||
|
bitrate = -1
|
||||||
|
id = 0
|
||||||
|
containerMimeType = null
|
||||||
|
sampleMimeType = audio/ac4
|
||||||
|
maxInputSize = -1
|
||||||
|
width = -1
|
||||||
|
height = -1
|
||||||
|
frameRate = -1.0
|
||||||
|
rotationDegrees = 0
|
||||||
|
pixelWidthHeightRatio = 1.0
|
||||||
|
channelCount = 2
|
||||||
|
sampleRate = 48000
|
||||||
|
pcmEncoding = -1
|
||||||
|
encoderDelay = 0
|
||||||
|
encoderPadding = 0
|
||||||
|
subsampleOffsetUs = 9223372036854775807
|
||||||
|
selectionFlags = 0
|
||||||
|
language = null
|
||||||
|
drmInitData = -
|
||||||
|
initializationData:
|
||||||
|
total output bytes = 7594
|
||||||
|
sample count = 19
|
||||||
|
sample 0:
|
||||||
|
time = 0
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash B4277F9E
|
||||||
|
sample 1:
|
||||||
|
time = 40000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash E8E0A142
|
||||||
|
sample 2:
|
||||||
|
time = 80000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash 2E5073D0
|
||||||
|
sample 3:
|
||||||
|
time = 120000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash 850E71D8
|
||||||
|
sample 4:
|
||||||
|
time = 160000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash 69CD444E
|
||||||
|
sample 5:
|
||||||
|
time = 200000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash BD24F36D
|
||||||
|
sample 6:
|
||||||
|
time = 240000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash E24F2490
|
||||||
|
sample 7:
|
||||||
|
time = 280000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash EE6F1F06
|
||||||
|
sample 8:
|
||||||
|
time = 320000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash 2DAB000F
|
||||||
|
sample 9:
|
||||||
|
time = 360000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash 8102B7EC
|
||||||
|
sample 10:
|
||||||
|
time = 400000
|
||||||
|
flags = 1
|
||||||
|
data = length 366, hash 55BF59AC
|
||||||
|
sample 11:
|
||||||
|
time = 440000
|
||||||
|
flags = 1
|
||||||
|
data = length 494, hash CBC2E09F
|
||||||
|
sample 12:
|
||||||
|
time = 480000
|
||||||
|
flags = 1
|
||||||
|
data = length 519, hash 9DAF56E9
|
||||||
|
sample 13:
|
||||||
|
time = 520000
|
||||||
|
flags = 1
|
||||||
|
data = length 598, hash 8169EE2
|
||||||
|
sample 14:
|
||||||
|
time = 560000
|
||||||
|
flags = 1
|
||||||
|
data = length 435, hash 28C21246
|
||||||
|
sample 15:
|
||||||
|
time = 600000
|
||||||
|
flags = 1
|
||||||
|
data = length 365, hash FF14716D
|
||||||
|
sample 16:
|
||||||
|
time = 640000
|
||||||
|
flags = 1
|
||||||
|
data = length 392, hash 4CC96B29
|
||||||
|
sample 17:
|
||||||
|
time = 680000
|
||||||
|
flags = 1
|
||||||
|
data = length 373, hash D7AC6D4E
|
||||||
|
sample 18:
|
||||||
|
time = 720000
|
||||||
|
flags = 1
|
||||||
|
data = length 392, hash 99F2511F
|
||||||
|
tracksEnded = true
|
@ -25,6 +25,7 @@ import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
|
|||||||
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
|
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ogg.OggExtractor;
|
import com.google.android.exoplayer2.extractor.ogg.OggExtractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
|
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.AdtsExtractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.PsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.PsExtractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
||||||
@ -62,7 +63,8 @@ public final class DefaultExtractorsFactoryTest {
|
|||||||
OggExtractor.class,
|
OggExtractor.class,
|
||||||
PsExtractor.class,
|
PsExtractor.class,
|
||||||
WavExtractor.class,
|
WavExtractor.class,
|
||||||
AmrExtractor.class
|
AmrExtractor.class,
|
||||||
|
Ac4Extractor.class
|
||||||
};
|
};
|
||||||
|
|
||||||
assertThat(listCreatedExtractorClasses).containsNoDuplicates();
|
assertThat(listCreatedExtractorClasses).containsNoDuplicates();
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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.extractor.ts;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.testutil.ExtractorAsserts;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
|
||||||
|
/** Unit test for {@link Ac4Extractor}. */
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public final class Ac4ExtractorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAc4Sample() throws Exception {
|
||||||
|
ExtractorAsserts.assertBehavior(Ac4Extractor::new, "ts/sample.ac4");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user