/** * Creates a Flac decoder. * * @param numInputBuffers The number of input buffers. * @param numOutputBuffers The number of output buffers. * @param initializationData Codec-specific initialization data. It should contain only one entry * which is the flac file header. * @throws FlacDecoderException Thrown if an exception occurs when initializing the decoder. */ public FlacDecoder(int numInputBuffers, int numOutputBuffers, List<byte[]> initializationData) throws FlacDecoderException { super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]); if (initializationData.size() != 1) { throw new FlacDecoderException("Initialization data must be of length 1"); } decoderJni = new FlacDecoderJni(); decoderJni.setData(ByteBuffer.wrap(initializationData.get(0))); FlacStreamInfo streamInfo; try { streamInfo = decoderJni.decodeMetadata(); } catch (IOException | InterruptedException e) { // Never happens. throw new IllegalStateException(e); } if (streamInfo == null) { throw new FlacDecoderException("Metadata decoding failed"); } setInitialInputBufferSize(streamInfo.maxFrameSize); maxOutputBufferSize = streamInfo.maxDecodedFrameSize(); }
@Override public FlacDecoderException decode(DecoderInputBuffer inputBuffer, SimpleOutputBuffer outputBuffer, boolean reset) { if (reset) { decoderJni.flush(); } decoderJni.setData(inputBuffer.data); ByteBuffer outputData = outputBuffer.init(inputBuffer.timeUs, maxOutputBufferSize); int result; try { result = decoderJni.decodeSample(outputData); } catch (IOException | InterruptedException e) { // Never happens. throw new IllegalStateException(e); } if (result < 0) { return new FlacDecoderException("Frame decoding failed"); } outputData.position(0); outputData.limit(result); return null; }
@Override public 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 = inputBuffer.data; int inputSize = inputData.limit(); ByteBuffer outputData = outputBuffer.init(inputBuffer.timeUs, OUTPUT_BUFFER_SIZE); int result = ffmpegDecode(nativeContext, inputData, inputSize, outputData, OUTPUT_BUFFER_SIZE); if (result < 0) { return new FfmpegDecoderException("Error decoding (see logcat). Code: " + result); } if (!hasOutputFormat) { channelCount = ffmpegGetChannelCount(nativeContext); sampleRate = ffmpegGetSampleRate(nativeContext); hasOutputFormat = true; } outputBuffer.data.position(0); outputBuffer.data.limit(result); return null; }
public FfmpegDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize, String mimeType, List<byte[]> initializationData, boolean outputFloat) throws FfmpegDecoderException { super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]); if (!FfmpegLibrary.isAvailable()) { throw new FfmpegDecoderException("Failed to load decoder native libraries."); } codecName = FfmpegLibrary.getCodecName(mimeType); extraData = getExtraData(mimeType, 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); if (nativeContext == 0) { throw new FfmpegDecoderException("Initialization failed."); } setInitialInputBufferSize(initialInputBufferSize); }
@Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); audioRenderer = new SimpleDecoderAudioRenderer(null, null, null, false, mockAudioSink) { @Override protected int supportsFormatInternal(DrmSessionManager<ExoMediaCrypto> drmSessionManager, Format format) { return FORMAT_HANDLED; } @Override protected SimpleDecoder<DecoderInputBuffer, ? extends SimpleOutputBuffer, ? extends AudioDecoderException> createDecoder(Format format, ExoMediaCrypto mediaCrypto) throws AudioDecoderException { return new FakeDecoder(); } }; }
public FfmpegDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize, String mimeType, List<byte[]> initializationData) throws FfmpegDecoderException { super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]); if (!FfmpegLibrary.isAvailable()) { throw new FfmpegDecoderException("Failed to load decoder native libraries."); } codecName = FfmpegLibrary.getCodecName(mimeType); extraData = getExtraData(mimeType, initializationData); nativeContext = ffmpegInitialize(codecName, extraData); if (nativeContext == 0) { throw new FfmpegDecoderException("Initialization failed."); } setInitialInputBufferSize(initialInputBufferSize); }
@Override public 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 = inputBuffer.data; int inputSize = inputData.limit(); ByteBuffer outputData = outputBuffer.init(inputBuffer.timeUs, outputBufferSize); int result = ffmpegDecode(nativeContext, inputData, inputSize, outputData, outputBufferSize); if (result < 0) { return new FfmpegDecoderException("Error decoding (see logcat). Code: " + result); } if (!hasOutputFormat) { channelCount = ffmpegGetChannelCount(nativeContext); sampleRate = ffmpegGetSampleRate(nativeContext); if (sampleRate == 0 && "alac".equals(codecName)) { // 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; } outputBuffer.data.position(0); outputBuffer.data.limit(result); return null; }
@Override protected AudioDecoderException decode(DecoderInputBuffer inputBuffer, SimpleOutputBuffer outputBuffer, boolean reset) { if (inputBuffer.isEndOfStream()) { outputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); } return null; }
/** * Creates an Opus decoder. * * @param numInputBuffers The number of input buffers. * @param numOutputBuffers The number of output buffers. * @param initialInputBufferSize The initial size of each input buffer. * @param initializationData Codec-specific initialization data. The first element must contain an * opus header. Optionally, the list may contain two additional buffers, which must contain * the encoder delay and seek pre roll values in nanoseconds, encoded as longs. * @param exoMediaCrypto The {@link ExoMediaCrypto} object required for decoding encrypted * content. Maybe null and can be ignored if decoder does not handle encrypted content. * @throws OpusDecoderException Thrown if an exception occurs when initializing the decoder. */ public OpusDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize, List<byte[]> initializationData, ExoMediaCrypto exoMediaCrypto) throws OpusDecoderException { super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]); if (!OpusLibrary.isAvailable()) { throw new OpusDecoderException("Failed to load decoder native libraries."); } this.exoMediaCrypto = exoMediaCrypto; if (exoMediaCrypto != null && !OpusLibrary.opusIsSecureDecodeSupported()) { throw new OpusDecoderException("Opus decoder does not support secure decode."); } byte[] headerBytes = initializationData.get(0); if (headerBytes.length < 19) { throw new OpusDecoderException("Header size is too small."); } channelCount = headerBytes[9] & 0xFF; if (channelCount > 8) { throw new OpusDecoderException("Invalid channel count: " + channelCount); } int preskip = readLittleEndian16(headerBytes, 10); int gain = readLittleEndian16(headerBytes, 16); byte[] streamMap = new byte[8]; int numStreams; int numCoupled; if (headerBytes[18] == 0) { // Channel mapping // If there is no channel mapping, use the defaults. if (channelCount > 2) { // Maximum channel count with default layout. throw new OpusDecoderException("Invalid Header, missing stream map."); } numStreams = 1; numCoupled = (channelCount == 2) ? 1 : 0; streamMap[0] = 0; streamMap[1] = 1; } else { if (headerBytes.length < 21 + channelCount) { throw new OpusDecoderException("Header size is too small."); } // Read the channel mapping. numStreams = headerBytes[19] & 0xFF; numCoupled = headerBytes[20] & 0xFF; System.arraycopy(headerBytes, 21, streamMap, 0, channelCount); } if (initializationData.size() == 3) { if (initializationData.get(1).length != 8 || initializationData.get(2).length != 8) { throw new OpusDecoderException("Invalid Codec Delay or Seek Preroll"); } long codecDelayNs = ByteBuffer.wrap(initializationData.get(1)).order(ByteOrder.nativeOrder()).getLong(); long seekPreRollNs = ByteBuffer.wrap(initializationData.get(2)).order(ByteOrder.nativeOrder()).getLong(); headerSkipSamples = nsToSamples(codecDelayNs); headerSeekPreRollSamples = nsToSamples(seekPreRollNs); } else { headerSkipSamples = preskip; headerSeekPreRollSamples = DEFAULT_SEEK_PRE_ROLL_SAMPLES; } nativeDecoderContext = opusInit(SAMPLE_RATE, channelCount, numStreams, numCoupled, gain, streamMap); if (nativeDecoderContext == 0) { throw new OpusDecoderException("Failed to initialize decoder"); } setInitialInputBufferSize(initialInputBufferSize); }
@Override public SimpleOutputBuffer createOutputBuffer() { return new SimpleOutputBuffer(this); }
@Override public OpusDecoderException decode(DecoderInputBuffer inputBuffer, SimpleOutputBuffer outputBuffer, boolean reset) { if (reset) { opusReset(nativeDecoderContext); // When seeking to 0, skip number of samples as specified in opus header. When seeking to // any other time, skip number of samples as specified by seek preroll. skipSamples = (inputBuffer.timeUs == 0) ? headerSkipSamples : headerSeekPreRollSamples; } ByteBuffer inputData = inputBuffer.data; CryptoInfo cryptoInfo = inputBuffer.cryptoInfo; int result = inputBuffer.isEncrypted() ? opusSecureDecode(nativeDecoderContext, inputBuffer.timeUs, inputData, inputData.limit(), outputBuffer, SAMPLE_RATE, exoMediaCrypto, cryptoInfo.mode, cryptoInfo.key, cryptoInfo.iv, cryptoInfo.numSubSamples, cryptoInfo.numBytesOfClearData, cryptoInfo.numBytesOfEncryptedData) : opusDecode(nativeDecoderContext, inputBuffer.timeUs, inputData, inputData.limit(), outputBuffer, SAMPLE_RATE); if (result < 0) { if (result == DRM_ERROR) { String message = "Drm error: " + opusGetErrorMessage(nativeDecoderContext); DecryptionException cause = new DecryptionException( opusGetErrorCode(nativeDecoderContext), message); return new OpusDecoderException(message, cause); } else { return new OpusDecoderException("Decode error: " + opusGetErrorMessage(result)); } } ByteBuffer outputData = outputBuffer.data; outputData.position(0); outputData.limit(result); if (skipSamples > 0) { int bytesPerSample = channelCount * 2; int skipBytes = skipSamples * bytesPerSample; if (result <= skipBytes) { skipSamples -= result / bytesPerSample; outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); outputData.position(result); } else { skipSamples = 0; outputData.position(skipBytes); } } return null; }
private native int opusDecode(long decoder, long timeUs, ByteBuffer inputBuffer, int inputSize, SimpleOutputBuffer outputBuffer, int sampleRate);
private native int opusSecureDecode(long decoder, long timeUs, ByteBuffer inputBuffer, int inputSize, SimpleOutputBuffer outputBuffer, int sampleRate, ExoMediaCrypto wvCrypto, int inputMode, byte[] key, byte[] iv, int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData);
@Override public OpusDecoderException decode(DecoderInputBuffer inputBuffer, SimpleOutputBuffer outputBuffer, boolean reset) { if (reset) { opusReset(nativeDecoderContext); // When seeking to 0, skip number of samples as specified in opus header. When seeking to // any other time, skip number of samples as specified by seek preroll. skipSamples = (inputBuffer.timeUs == 0) ? headerSkipSamples : headerSeekPreRollSamples; } ByteBuffer inputData = inputBuffer.data; CryptoInfo cryptoInfo = inputBuffer.cryptoInfo; int result = inputBuffer.isEncrypted() ? opusSecureDecode(nativeDecoderContext, inputBuffer.timeUs, inputData, inputData.limit(), outputBuffer, SAMPLE_RATE, exoMediaCrypto, cryptoInfo.mode, cryptoInfo.key, cryptoInfo.iv, cryptoInfo.numSubSamples, cryptoInfo.numBytesOfClearData, cryptoInfo.numBytesOfEncryptedData) : opusDecode(nativeDecoderContext, inputBuffer.timeUs, inputData, inputData.limit(), outputBuffer); if (result < 0) { if (result == DRM_ERROR) { String message = "Drm error: " + opusGetErrorMessage(nativeDecoderContext); DecryptionException cause = new DecryptionException( opusGetErrorCode(nativeDecoderContext), message); return new OpusDecoderException(message, cause); } else { return new OpusDecoderException("Decode error: " + opusGetErrorMessage(result)); } } ByteBuffer outputData = outputBuffer.data; outputData.position(0); outputData.limit(result); if (skipSamples > 0) { int bytesPerSample = channelCount * 2; int skipBytes = skipSamples * bytesPerSample; if (result <= skipBytes) { skipSamples -= result / bytesPerSample; outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); outputData.position(result); } else { skipSamples = 0; outputData.position(skipBytes); } } return null; }
private native int opusDecode(long decoder, long timeUs, ByteBuffer inputBuffer, int inputSize, SimpleOutputBuffer outputBuffer);
private native int opusSecureDecode(long decoder, long timeUs, ByteBuffer inputBuffer, int inputSize, SimpleOutputBuffer outputBuffer, int sampleRate, ExoMediaCrypto mediaCrypto, int inputMode, byte[] key, byte[] iv, int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData);
public FakeDecoder() { super(new DecoderInputBuffer[1], new SimpleOutputBuffer[1]); }
@Override protected SimpleOutputBuffer createOutputBuffer() { return new SimpleOutputBuffer(this); }
/** * Creates a decoder for the given format. * * @param format The format for which a decoder is required. * @param mediaCrypto The {@link ExoMediaCrypto} object required for decoding encrypted content. * Maybe null and can be ignored if decoder does not handle encrypted content. * @return The decoder. * @throws AudioDecoderException If an error occurred creating a suitable decoder. */ protected abstract SimpleDecoder<DecoderInputBuffer, ? extends SimpleOutputBuffer, ? extends AudioDecoderException> createDecoder(Format format, ExoMediaCrypto mediaCrypto) throws AudioDecoderException;
/** * Creates a decoder for the given format. * * @param format The format for which a decoder is required. * @return The decoder. * @throws AudioDecoderException If an error occurred creating a suitable decoder. */ protected abstract SimpleDecoder<DecoderInputBuffer, ? extends SimpleOutputBuffer, ? extends AudioDecoderException> createDecoder(Format format) throws AudioDecoderException;