1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2.ext.ffmpeg; 17 18 import androidx.annotation.Nullable; 19 import com.google.android.exoplayer2.C; 20 import com.google.android.exoplayer2.Format; 21 import com.google.android.exoplayer2.decoder.DecoderInputBuffer; 22 import com.google.android.exoplayer2.decoder.SimpleDecoder; 23 import com.google.android.exoplayer2.decoder.SimpleOutputBuffer; 24 import com.google.android.exoplayer2.util.Assertions; 25 import com.google.android.exoplayer2.util.MimeTypes; 26 import com.google.android.exoplayer2.util.ParsableByteArray; 27 import com.google.android.exoplayer2.util.Util; 28 import java.nio.ByteBuffer; 29 import java.util.List; 30 31 /** FFmpeg audio decoder. */ 32 /* package */ final class FfmpegAudioDecoder 33 extends SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, FfmpegDecoderException> { 34 35 // Output buffer sizes when decoding PCM mu-law streams, which is the maximum FFmpeg outputs. 36 private static final int OUTPUT_BUFFER_SIZE_16BIT = 65536; 37 private static final int OUTPUT_BUFFER_SIZE_32BIT = OUTPUT_BUFFER_SIZE_16BIT * 2; 38 39 // LINT.IfChange 40 private static final int AUDIO_DECODER_ERROR_INVALID_DATA = -1; 41 private static final int AUDIO_DECODER_ERROR_OTHER = -2; 42 // LINT.ThenChange(../../../../../../../jni/ffmpeg_jni.cc) 43 44 private final String codecName; 45 @Nullable private final byte[] extraData; 46 private final @C.Encoding int encoding; 47 private final int outputBufferSize; 48 49 private long nativeContext; // May be reassigned on resetting the codec. 50 private boolean hasOutputFormat; 51 private volatile int channelCount; 52 private volatile int sampleRate; 53 FfmpegAudioDecoder( int numInputBuffers, int numOutputBuffers, int initialInputBufferSize, Format format, boolean outputFloat)54 public FfmpegAudioDecoder( 55 int numInputBuffers, 56 int numOutputBuffers, 57 int initialInputBufferSize, 58 Format format, 59 boolean outputFloat) 60 throws FfmpegDecoderException { 61 super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]); 62 if (!FfmpegLibrary.isAvailable()) { 63 throw new FfmpegDecoderException("Failed to load decoder native libraries."); 64 } 65 Assertions.checkNotNull(format.sampleMimeType); 66 codecName = Assertions.checkNotNull(FfmpegLibrary.getCodecName(format.sampleMimeType)); 67 extraData = getExtraData(format.sampleMimeType, format.initializationData); 68 encoding = outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT; 69 outputBufferSize = outputFloat ? OUTPUT_BUFFER_SIZE_32BIT : OUTPUT_BUFFER_SIZE_16BIT; 70 nativeContext = 71 ffmpegInitialize(codecName, extraData, outputFloat, format.sampleRate, format.channelCount); 72 if (nativeContext == 0) { 73 throw new FfmpegDecoderException("Initialization failed."); 74 } 75 setInitialInputBufferSize(initialInputBufferSize); 76 } 77 78 @Override getName()79 public String getName() { 80 return "ffmpeg" + FfmpegLibrary.getVersion() + "-" + codecName; 81 } 82 83 @Override createInputBuffer()84 protected DecoderInputBuffer createInputBuffer() { 85 return new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT); 86 } 87 88 @Override createOutputBuffer()89 protected SimpleOutputBuffer createOutputBuffer() { 90 return new SimpleOutputBuffer(this::releaseOutputBuffer); 91 } 92 93 @Override createUnexpectedDecodeException(Throwable error)94 protected FfmpegDecoderException createUnexpectedDecodeException(Throwable error) { 95 return new FfmpegDecoderException("Unexpected decode error", error); 96 } 97 98 @Override decode( DecoderInputBuffer inputBuffer, SimpleOutputBuffer outputBuffer, boolean reset)99 protected @Nullable FfmpegDecoderException decode( 100 DecoderInputBuffer inputBuffer, SimpleOutputBuffer outputBuffer, boolean reset) { 101 if (reset) { 102 nativeContext = ffmpegReset(nativeContext, extraData); 103 if (nativeContext == 0) { 104 return new FfmpegDecoderException("Error resetting (see logcat)."); 105 } 106 } 107 ByteBuffer inputData = Util.castNonNull(inputBuffer.data); 108 int inputSize = inputData.limit(); 109 ByteBuffer outputData = outputBuffer.init(inputBuffer.timeUs, outputBufferSize); 110 int result = ffmpegDecode(nativeContext, inputData, inputSize, outputData, outputBufferSize); 111 if (result == AUDIO_DECODER_ERROR_INVALID_DATA) { 112 // Treat invalid data errors as non-fatal to match the behavior of MediaCodec. No output will 113 // be produced for this buffer, so mark it as decode-only to ensure that the audio sink's 114 // position is reset when more audio is produced. 115 outputBuffer.setFlags(C.BUFFER_FLAG_DECODE_ONLY); 116 return null; 117 } else if (result == AUDIO_DECODER_ERROR_OTHER) { 118 return new FfmpegDecoderException("Error decoding (see logcat)."); 119 } 120 if (!hasOutputFormat) { 121 channelCount = ffmpegGetChannelCount(nativeContext); 122 sampleRate = ffmpegGetSampleRate(nativeContext); 123 if (sampleRate == 0 && "alac".equals(codecName)) { 124 Assertions.checkNotNull(extraData); 125 // ALAC decoder did not set the sample rate in earlier versions of FFmpeg. See 126 // https://trac.ffmpeg.org/ticket/6096. 127 ParsableByteArray parsableExtraData = new ParsableByteArray(extraData); 128 parsableExtraData.setPosition(extraData.length - 4); 129 sampleRate = parsableExtraData.readUnsignedIntToInt(); 130 } 131 hasOutputFormat = true; 132 } 133 outputData.position(0); 134 outputData.limit(result); 135 return null; 136 } 137 138 @Override release()139 public void release() { 140 super.release(); 141 ffmpegRelease(nativeContext); 142 nativeContext = 0; 143 } 144 145 /** Returns the channel count of output audio. */ getChannelCount()146 public int getChannelCount() { 147 return channelCount; 148 } 149 150 /** Returns the sample rate of output audio. */ getSampleRate()151 public int getSampleRate() { 152 return sampleRate; 153 } 154 155 /** Returns the encoding of output audio. */ getEncoding()156 public @C.Encoding int getEncoding() { 157 return encoding; 158 } 159 160 /** 161 * Returns FFmpeg-compatible codec-specific initialization data ("extra data"), or {@code null} if 162 * not required. 163 */ getExtraData(String mimeType, List<byte[]> initializationData)164 private static @Nullable byte[] getExtraData(String mimeType, List<byte[]> initializationData) { 165 switch (mimeType) { 166 case MimeTypes.AUDIO_AAC: 167 case MimeTypes.AUDIO_OPUS: 168 return initializationData.get(0); 169 case MimeTypes.AUDIO_ALAC: 170 return getAlacExtraData(initializationData); 171 case MimeTypes.AUDIO_VORBIS: 172 return getVorbisExtraData(initializationData); 173 default: 174 // Other codecs do not require extra data. 175 return null; 176 } 177 } 178 getAlacExtraData(List<byte[]> initializationData)179 private static byte[] getAlacExtraData(List<byte[]> initializationData) { 180 // FFmpeg's ALAC decoder expects an ALAC atom, which contains the ALAC "magic cookie", as extra 181 // data. initializationData[0] contains only the magic cookie, and so we need to package it into 182 // an ALAC atom. See: 183 // https://ffmpeg.org/doxygen/0.6/alac_8c.html 184 // https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt 185 byte[] magicCookie = initializationData.get(0); 186 int alacAtomLength = 12 + magicCookie.length; 187 ByteBuffer alacAtom = ByteBuffer.allocate(alacAtomLength); 188 alacAtom.putInt(alacAtomLength); 189 alacAtom.putInt(0x616c6163); // type=alac 190 alacAtom.putInt(0); // version=0, flags=0 191 alacAtom.put(magicCookie, /* offset= */ 0, magicCookie.length); 192 return alacAtom.array(); 193 } 194 getVorbisExtraData(List<byte[]> initializationData)195 private static byte[] getVorbisExtraData(List<byte[]> initializationData) { 196 byte[] header0 = initializationData.get(0); 197 byte[] header1 = initializationData.get(1); 198 byte[] extraData = new byte[header0.length + header1.length + 6]; 199 extraData[0] = (byte) (header0.length >> 8); 200 extraData[1] = (byte) (header0.length & 0xFF); 201 System.arraycopy(header0, 0, extraData, 2, header0.length); 202 extraData[header0.length + 2] = 0; 203 extraData[header0.length + 3] = 0; 204 extraData[header0.length + 4] = (byte) (header1.length >> 8); 205 extraData[header0.length + 5] = (byte) (header1.length & 0xFF); 206 System.arraycopy(header1, 0, extraData, header0.length + 6, header1.length); 207 return extraData; 208 } 209 ffmpegInitialize( String codecName, @Nullable byte[] extraData, boolean outputFloat, int rawSampleRate, int rawChannelCount)210 private native long ffmpegInitialize( 211 String codecName, 212 @Nullable byte[] extraData, 213 boolean outputFloat, 214 int rawSampleRate, 215 int rawChannelCount); 216 ffmpegDecode( long context, ByteBuffer inputData, int inputSize, ByteBuffer outputData, int outputSize)217 private native int ffmpegDecode( 218 long context, ByteBuffer inputData, int inputSize, ByteBuffer outputData, int outputSize); 219 ffmpegGetChannelCount(long context)220 private native int ffmpegGetChannelCount(long context); 221 ffmpegGetSampleRate(long context)222 private native int ffmpegGetSampleRate(long context); 223 ffmpegReset(long context, @Nullable byte[] extraData)224 private native long ffmpegReset(long context, @Nullable byte[] extraData); 225 ffmpegRelease(long context)226 private native void ffmpegRelease(long context); 227 } 228