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