summaryrefslogtreecommitdiff
path: root/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java
diff options
context:
space:
mode:
Diffstat (limited to 'tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java')
-rw-r--r--tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java227
1 files changed, 0 insertions, 227 deletions
diff --git a/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java b/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java
deleted file mode 100644
index c5072a33..00000000
--- a/tree/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2016 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.ext.ffmpeg;
-
-import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.Format;
-import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
-import com.google.android.exoplayer2.decoder.SimpleDecoder;
-import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
-import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.MimeTypes;
-import com.google.android.exoplayer2.util.ParsableByteArray;
-import com.google.android.exoplayer2.util.Util;
-import java.nio.ByteBuffer;
-import java.util.List;
-
-/** FFmpeg audio decoder. */
-/* package */ final class FfmpegAudioDecoder
- extends SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, FfmpegDecoderException> {
-
- // Output buffer sizes when decoding PCM mu-law streams, which is the maximum FFmpeg outputs.
- private static final int OUTPUT_BUFFER_SIZE_16BIT = 65536;
- private static final int OUTPUT_BUFFER_SIZE_32BIT = OUTPUT_BUFFER_SIZE_16BIT * 2;
-
- // LINT.IfChange
- private static final int AUDIO_DECODER_ERROR_INVALID_DATA = -1;
- private static final int AUDIO_DECODER_ERROR_OTHER = -2;
- // LINT.ThenChange(../../../../../../../jni/ffmpeg_jni.cc)
-
- private final String codecName;
- @Nullable private final byte[] extraData;
- private final @C.Encoding int encoding;
- private final int outputBufferSize;
-
- private long nativeContext; // May be reassigned on resetting the codec.
- private boolean hasOutputFormat;
- private volatile int channelCount;
- private volatile int sampleRate;
-
- public FfmpegAudioDecoder(
- int numInputBuffers,
- int numOutputBuffers,
- int initialInputBufferSize,
- Format format,
- boolean outputFloat)
- throws FfmpegDecoderException {
- super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]);
- if (!FfmpegLibrary.isAvailable()) {
- throw new FfmpegDecoderException("Failed to load decoder native libraries.");
- }
- Assertions.checkNotNull(format.sampleMimeType);
- codecName = Assertions.checkNotNull(FfmpegLibrary.getCodecName(format.sampleMimeType));
- extraData = getExtraData(format.sampleMimeType, format.initializationData);
- encoding = outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT;
- outputBufferSize = outputFloat ? OUTPUT_BUFFER_SIZE_32BIT : OUTPUT_BUFFER_SIZE_16BIT;
- nativeContext =
- ffmpegInitialize(codecName, extraData, outputFloat, format.sampleRate, format.channelCount);
- if (nativeContext == 0) {
- throw new FfmpegDecoderException("Initialization failed.");
- }
- setInitialInputBufferSize(initialInputBufferSize);
- }
-
- @Override
- public String getName() {
- return "ffmpeg" + FfmpegLibrary.getVersion() + "-" + codecName;
- }
-
- @Override
- protected DecoderInputBuffer createInputBuffer() {
- return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
- }
-
- @Override
- protected SimpleOutputBuffer createOutputBuffer() {
- return new SimpleOutputBuffer(this::releaseOutputBuffer);
- }
-
- @Override
- protected FfmpegDecoderException createUnexpectedDecodeException(Throwable error) {
- return new FfmpegDecoderException("Unexpected decode error", error);
- }
-
- @Override
- protected @Nullable FfmpegDecoderException decode(
- DecoderInputBuffer inputBuffer, SimpleOutputBuffer outputBuffer, boolean reset) {
- if (reset) {
- nativeContext = ffmpegReset(nativeContext, extraData);
- if (nativeContext == 0) {
- return new FfmpegDecoderException("Error resetting (see logcat).");
- }
- }
- ByteBuffer inputData = Util.castNonNull(inputBuffer.data);
- int inputSize = inputData.limit();
- ByteBuffer outputData = outputBuffer.init(inputBuffer.timeUs, outputBufferSize);
- int result = ffmpegDecode(nativeContext, inputData, inputSize, outputData, outputBufferSize);
- if (result == AUDIO_DECODER_ERROR_INVALID_DATA) {
- // Treat invalid data errors as non-fatal to match the behavior of MediaCodec. No output will
- // be produced for this buffer, so mark it as decode-only to ensure that the audio sink's
- // position is reset when more audio is produced.
- outputBuffer.setFlags(C.BUFFER_FLAG_DECODE_ONLY);
- return null;
- } else if (result == AUDIO_DECODER_ERROR_OTHER) {
- return new FfmpegDecoderException("Error decoding (see logcat).");
- }
- if (!hasOutputFormat) {
- channelCount = ffmpegGetChannelCount(nativeContext);
- sampleRate = ffmpegGetSampleRate(nativeContext);
- if (sampleRate == 0 && "alac".equals(codecName)) {
- Assertions.checkNotNull(extraData);
- // ALAC decoder did not set the sample rate in earlier versions of FFmpeg. See
- // https://trac.ffmpeg.org/ticket/6096.
- ParsableByteArray parsableExtraData = new ParsableByteArray(extraData);
- parsableExtraData.setPosition(extraData.length - 4);
- sampleRate = parsableExtraData.readUnsignedIntToInt();
- }
- hasOutputFormat = true;
- }
- outputData.position(0);
- outputData.limit(result);
- return null;
- }
-
- @Override
- public void release() {
- super.release();
- ffmpegRelease(nativeContext);
- nativeContext = 0;
- }
-
- /** Returns the channel count of output audio. */
- public int getChannelCount() {
- return channelCount;
- }
-
- /** Returns the sample rate of output audio. */
- public int getSampleRate() {
- return sampleRate;
- }
-
- /** Returns the encoding of output audio. */
- public @C.Encoding int getEncoding() {
- return encoding;
- }
-
- /**
- * Returns FFmpeg-compatible codec-specific initialization data ("extra data"), or {@code null} if
- * not required.
- */
- private static @Nullable byte[] getExtraData(String mimeType, List<byte[]> initializationData) {
- switch (mimeType) {
- case MimeTypes.AUDIO_AAC:
- case MimeTypes.AUDIO_OPUS:
- return initializationData.get(0);
- case MimeTypes.AUDIO_ALAC:
- return getAlacExtraData(initializationData);
- case MimeTypes.AUDIO_VORBIS:
- return getVorbisExtraData(initializationData);
- default:
- // Other codecs do not require extra data.
- return null;
- }
- }
-
- private static byte[] getAlacExtraData(List<byte[]> initializationData) {
- // FFmpeg's ALAC decoder expects an ALAC atom, which contains the ALAC "magic cookie", as extra
- // data. initializationData[0] contains only the magic cookie, and so we need to package it into
- // an ALAC atom. See:
- // https://ffmpeg.org/doxygen/0.6/alac_8c.html
- // https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt
- byte[] magicCookie = initializationData.get(0);
- int alacAtomLength = 12 + magicCookie.length;
- ByteBuffer alacAtom = ByteBuffer.allocate(alacAtomLength);
- alacAtom.putInt(alacAtomLength);
- alacAtom.putInt(0x616c6163); // type=alac
- alacAtom.putInt(0); // version=0, flags=0
- alacAtom.put(magicCookie, /* offset= */ 0, magicCookie.length);
- return alacAtom.array();
- }
-
- private static byte[] getVorbisExtraData(List<byte[]> initializationData) {
- byte[] header0 = initializationData.get(0);
- byte[] header1 = initializationData.get(1);
- byte[] extraData = new byte[header0.length + header1.length + 6];
- extraData[0] = (byte) (header0.length >> 8);
- extraData[1] = (byte) (header0.length & 0xFF);
- System.arraycopy(header0, 0, extraData, 2, header0.length);
- extraData[header0.length + 2] = 0;
- extraData[header0.length + 3] = 0;
- extraData[header0.length + 4] = (byte) (header1.length >> 8);
- extraData[header0.length + 5] = (byte) (header1.length & 0xFF);
- System.arraycopy(header1, 0, extraData, header0.length + 6, header1.length);
- return extraData;
- }
-
- private native long ffmpegInitialize(
- String codecName,
- @Nullable byte[] extraData,
- boolean outputFloat,
- int rawSampleRate,
- int rawChannelCount);
-
- private native int ffmpegDecode(
- long context, ByteBuffer inputData, int inputSize, ByteBuffer outputData, int outputSize);
-
- private native int ffmpegGetChannelCount(long context);
-
- private native int ffmpegGetSampleRate(long context);
-
- private native long ffmpegReset(long context, @Nullable byte[] extraData);
-
- private native void ffmpegRelease(long context);
-}