Remove Mp4Writer interface
Since the public class has already been split into `Mp4Muxer` and `FragmentedMp4Muxer`, there is no need for having common interface for internal implementation. In the follow up CL `BasicMp4Writer` will be renamed to `Mp4Writer` which is more appropriate and aligns with public class names. PiperOrigin-RevId: 619486876
This commit is contained in:
parent
5a318deb40
commit
66547cc331
@ -36,10 +36,8 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
/**
|
/** Writes all media samples into a single mdat box. */
|
||||||
* The basic implementation of {@link Mp4Writer} which writes all the samples in a single mdat box.
|
/* package */ final class BasicMp4Writer {
|
||||||
*/
|
|
||||||
/* package */ final class BasicMp4Writer implements Mp4Writer {
|
|
||||||
private static final long INTERLEAVE_DURATION_US = 1_000_000L;
|
private static final long INTERLEAVE_DURATION_US = 1_000_000L;
|
||||||
|
|
||||||
private final FileOutputStream outputStream;
|
private final FileOutputStream outputStream;
|
||||||
@ -78,7 +76,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
lastMoovWritten = Range.closed(0L, 0L);
|
lastMoovWritten = Range.closed(0L, 0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public TrackToken addTrack(int sortKey, Format format) {
|
public TrackToken addTrack(int sortKey, Format format) {
|
||||||
Track track = new Track(format, sortKey);
|
Track track = new Track(format, sortKey);
|
||||||
tracks.add(track);
|
tracks.add(track);
|
||||||
@ -86,7 +83,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
return track;
|
return track;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeSampleData(TrackToken token, ByteBuffer byteBuffer, BufferInfo bufferInfo)
|
public void writeSampleData(TrackToken token, ByteBuffer byteBuffer, BufferInfo bufferInfo)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
checkArgument(token instanceof Track);
|
checkArgument(token instanceof Track);
|
||||||
@ -94,7 +90,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
doInterleave();
|
doInterleave();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < tracks.size(); i++) {
|
for (int i = 0; i < tracks.size(); i++) {
|
||||||
|
@ -43,10 +43,10 @@ import java.util.List;
|
|||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link Mp4Writer} implementation which writes samples into multiple fragments as per the
|
* Writes media samples into multiple fragments as per the fragmented MP4 (ISO/IEC 14496-12)
|
||||||
* fragmented MP4 (ISO/IEC 14496-12) standard.
|
* standard.
|
||||||
*/
|
*/
|
||||||
/* package */ final class FragmentedMp4Writer implements Mp4Writer {
|
/* package */ final class FragmentedMp4Writer {
|
||||||
/** Provides a limited set of sample metadata. */
|
/** Provides a limited set of sample metadata. */
|
||||||
public static class SampleMetadata {
|
public static class SampleMetadata {
|
||||||
public final long durationVu;
|
public final long durationVu;
|
||||||
@ -88,7 +88,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
currentFragmentSequenceNumber = 1;
|
currentFragmentSequenceNumber = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public TrackToken addTrack(int sortKey, Format format) {
|
public TrackToken addTrack(int sortKey, Format format) {
|
||||||
Track track = new Track(format);
|
Track track = new Track(format);
|
||||||
tracks.add(track);
|
tracks.add(track);
|
||||||
@ -98,7 +97,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
return track;
|
return track;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeSampleData(
|
public void writeSampleData(
|
||||||
TrackToken token, ByteBuffer byteBuffer, MediaCodec.BufferInfo bufferInfo)
|
TrackToken token, ByteBuffer byteBuffer, MediaCodec.BufferInfo bufferInfo)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
@ -122,7 +120,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
lastPendingSample.presentationTimeUs - firstPendingSample.presentationTimeUs);
|
lastPendingSample.presentationTimeUs - firstPendingSample.presentationTimeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
try {
|
try {
|
||||||
createFragment();
|
createFragment();
|
||||||
|
@ -1,129 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2022 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 androidx.media3.muxer;
|
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
|
||||||
|
|
||||||
import android.media.MediaCodec;
|
|
||||||
import android.media.MediaCodec.BufferInfo;
|
|
||||||
import androidx.media3.common.C;
|
|
||||||
import androidx.media3.common.Format;
|
|
||||||
import androidx.media3.common.MimeTypes;
|
|
||||||
import androidx.media3.muxer.Muxer.TrackToken;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/** Writes MP4 data to the disk. */
|
|
||||||
/* package */ interface Mp4Writer {
|
|
||||||
|
|
||||||
TrackToken addTrack(int sortKey, Format format);
|
|
||||||
|
|
||||||
void writeSampleData(Mp4Muxer.TrackToken token, ByteBuffer byteBuffer, BufferInfo bufferInfo)
|
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
void close() throws IOException;
|
|
||||||
|
|
||||||
class Track implements TrackToken, Mp4MoovStructure.TrackMetadataProvider {
|
|
||||||
public final Format format;
|
|
||||||
public final int sortKey;
|
|
||||||
public final List<BufferInfo> writtenSamples;
|
|
||||||
public final List<Long> writtenChunkOffsets;
|
|
||||||
public final List<Integer> writtenChunkSampleCounts;
|
|
||||||
public final Deque<BufferInfo> pendingSamplesBufferInfo;
|
|
||||||
public final Deque<ByteBuffer> pendingSamplesByteBuffer;
|
|
||||||
public boolean hadKeyframe;
|
|
||||||
|
|
||||||
private long lastSamplePresentationTimeUs;
|
|
||||||
|
|
||||||
/** Creates an instance with {@code sortKey} set to 1. */
|
|
||||||
public Track(Format format) {
|
|
||||||
this(format, /* sortKey= */ 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an instance.
|
|
||||||
*
|
|
||||||
* @param format The {@link Format} for the track.
|
|
||||||
* @param sortKey The key used for sorting the track list.
|
|
||||||
*/
|
|
||||||
public Track(Format format, int sortKey) {
|
|
||||||
this.format = format;
|
|
||||||
this.sortKey = sortKey;
|
|
||||||
writtenSamples = new ArrayList<>();
|
|
||||||
writtenChunkOffsets = new ArrayList<>();
|
|
||||||
writtenChunkSampleCounts = new ArrayList<>();
|
|
||||||
pendingSamplesBufferInfo = new ArrayDeque<>();
|
|
||||||
pendingSamplesByteBuffer = new ArrayDeque<>();
|
|
||||||
lastSamplePresentationTimeUs = C.TIME_UNSET;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeSampleData(ByteBuffer byteBuffer, BufferInfo bufferInfo) throws IOException {
|
|
||||||
checkArgument(
|
|
||||||
bufferInfo.presentationTimeUs > lastSamplePresentationTimeUs,
|
|
||||||
"Out of order B-frames are not supported");
|
|
||||||
// TODO: b/279931840 - Confirm whether muxer should throw when writing empty samples.
|
|
||||||
// Skip empty samples.
|
|
||||||
if (bufferInfo.size == 0 || byteBuffer.remaining() == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) > 0) {
|
|
||||||
hadKeyframe = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The video track must start with a key frame.
|
|
||||||
if (!hadKeyframe && MimeTypes.isVideo(format.sampleMimeType)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingSamplesBufferInfo.addLast(bufferInfo);
|
|
||||||
pendingSamplesByteBuffer.addLast(byteBuffer);
|
|
||||||
lastSamplePresentationTimeUs = bufferInfo.presentationTimeUs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int videoUnitTimebase() {
|
|
||||||
return MimeTypes.isAudio(format.sampleMimeType)
|
|
||||||
? 48_000 // TODO: b/270583563 - Update these with actual values from mediaFormat.
|
|
||||||
: 90_000;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ImmutableList<BufferInfo> writtenSamples() {
|
|
||||||
return ImmutableList.copyOf(writtenSamples);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ImmutableList<Long> writtenChunkOffsets() {
|
|
||||||
return ImmutableList.copyOf(writtenChunkOffsets);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ImmutableList<Integer> writtenChunkSampleCounts() {
|
|
||||||
return ImmutableList.copyOf(writtenChunkSampleCounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Format format() {
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
118
libraries/muxer/src/main/java/androidx/media3/muxer/Track.java
Normal file
118
libraries/muxer/src/main/java/androidx/media3/muxer/Track.java
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 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 androidx.media3.muxer;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
|
|
||||||
|
import android.media.MediaCodec;
|
||||||
|
import android.media.MediaCodec.BufferInfo;
|
||||||
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.Format;
|
||||||
|
import androidx.media3.common.MimeTypes;
|
||||||
|
import androidx.media3.muxer.Muxer.TrackToken;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Represents a single track (audio, video, metadata etc.). */
|
||||||
|
/* package */ final class Track implements TrackToken, Mp4MoovStructure.TrackMetadataProvider {
|
||||||
|
public final Format format;
|
||||||
|
public final int sortKey;
|
||||||
|
public final List<BufferInfo> writtenSamples;
|
||||||
|
public final List<Long> writtenChunkOffsets;
|
||||||
|
public final List<Integer> writtenChunkSampleCounts;
|
||||||
|
public final Deque<BufferInfo> pendingSamplesBufferInfo;
|
||||||
|
public final Deque<ByteBuffer> pendingSamplesByteBuffer;
|
||||||
|
public boolean hadKeyframe;
|
||||||
|
|
||||||
|
private long lastSamplePresentationTimeUs;
|
||||||
|
|
||||||
|
/** Creates an instance with {@code sortKey} set to 1. */
|
||||||
|
public Track(Format format) {
|
||||||
|
this(format, /* sortKey= */ 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param format The {@link Format} for the track.
|
||||||
|
* @param sortKey The key used for sorting the track list.
|
||||||
|
*/
|
||||||
|
public Track(Format format, int sortKey) {
|
||||||
|
this.format = format;
|
||||||
|
this.sortKey = sortKey;
|
||||||
|
writtenSamples = new ArrayList<>();
|
||||||
|
writtenChunkOffsets = new ArrayList<>();
|
||||||
|
writtenChunkSampleCounts = new ArrayList<>();
|
||||||
|
pendingSamplesBufferInfo = new ArrayDeque<>();
|
||||||
|
pendingSamplesByteBuffer = new ArrayDeque<>();
|
||||||
|
lastSamplePresentationTimeUs = C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeSampleData(ByteBuffer byteBuffer, BufferInfo bufferInfo) {
|
||||||
|
checkArgument(
|
||||||
|
bufferInfo.presentationTimeUs > lastSamplePresentationTimeUs,
|
||||||
|
"Out of order B-frames are not supported");
|
||||||
|
// TODO: b/279931840 - Confirm whether muxer should throw when writing empty samples.
|
||||||
|
// Skip empty samples.
|
||||||
|
if (bufferInfo.size == 0 || byteBuffer.remaining() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) > 0) {
|
||||||
|
hadKeyframe = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The video track must start with a key frame.
|
||||||
|
if (!hadKeyframe && MimeTypes.isVideo(format.sampleMimeType)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingSamplesBufferInfo.addLast(bufferInfo);
|
||||||
|
pendingSamplesByteBuffer.addLast(byteBuffer);
|
||||||
|
lastSamplePresentationTimeUs = bufferInfo.presentationTimeUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int videoUnitTimebase() {
|
||||||
|
return MimeTypes.isAudio(format.sampleMimeType)
|
||||||
|
? 48_000 // TODO: b/270583563 - Update these with actual values from mediaFormat.
|
||||||
|
: 90_000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableList<BufferInfo> writtenSamples() {
|
||||||
|
return ImmutableList.copyOf(writtenSamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableList<Long> writtenChunkOffsets() {
|
||||||
|
return ImmutableList.copyOf(writtenChunkOffsets);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableList<Integer> writtenChunkSampleCounts() {
|
||||||
|
return ImmutableList.copyOf(writtenChunkSampleCounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Format format() {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user