Commit 7b10e637 by andrewlewis Committed by Oliver Woodman

Support mu-law and A-law PCM with ffmpeg

Issue: #4360

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=200186465
parent 877c6965
...@@ -27,6 +27,8 @@ ...@@ -27,6 +27,8 @@
([#4023](https://github.com/google/ExoPlayer/issues/4023)). ([#4023](https://github.com/google/ExoPlayer/issues/4023)).
* Allow apps to register custom MIME types * Allow apps to register custom MIME types
([#4264](https://github.com/google/ExoPlayer/issues/4264)). ([#4264](https://github.com/google/ExoPlayer/issues/4264)).
* Add support for mu-law and A-law PCM with the ffmpeg extension
([#4360](https://github.com/google/ExoPlayer/issues/4360)).
### 2.8.2 ### ### 2.8.2 ###
......
...@@ -37,6 +37,8 @@ android { ...@@ -37,6 +37,8 @@ android {
dependencies { dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation 'com.android.support:support-annotations:' + supportLibraryVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
} }
ext { ext {
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.ext.ffmpeg; package com.google.android.exoplayer2.ext.ffmpeg;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
...@@ -26,7 +27,10 @@ import com.google.android.exoplayer2.audio.DefaultAudioSink; ...@@ -26,7 +27,10 @@ import com.google.android.exoplayer2.audio.DefaultAudioSink;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer; import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import java.util.Collections;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* Decodes and renders audio using FFmpeg. * Decodes and renders audio using FFmpeg.
...@@ -45,10 +49,10 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer { ...@@ -45,10 +49,10 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
private final boolean enableFloatOutput; private final boolean enableFloatOutput;
private FfmpegDecoder decoder; private @MonotonicNonNull FfmpegDecoder decoder;
public FfmpegAudioRenderer() { public FfmpegAudioRenderer() {
this(null, null); this(/* eventHandler= */ null, /* eventListener= */ null);
} }
/** /**
...@@ -57,9 +61,15 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer { ...@@ -57,9 +61,15 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
* @param eventListener A listener of events. May be null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output. * @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output.
*/ */
public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener, public FfmpegAudioRenderer(
@Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener,
AudioProcessor... audioProcessors) { AudioProcessor... audioProcessors) {
this(eventHandler, eventListener, new DefaultAudioSink(null, audioProcessors), false); this(
eventHandler,
eventListener,
new DefaultAudioSink(/* audioCapabilities= */ null, audioProcessors),
/* enableFloatOutput= */ false);
} }
/** /**
...@@ -72,8 +82,11 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer { ...@@ -72,8 +82,11 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
* 32-bit float output, any audio processing will be disabled, including playback speed/pitch * 32-bit float output, any audio processing will be disabled, including playback speed/pitch
* adjustment. * adjustment.
*/ */
public FfmpegAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener, public FfmpegAudioRenderer(
AudioSink audioSink, boolean enableFloatOutput) { @Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener,
AudioSink audioSink,
boolean enableFloatOutput) {
super( super(
eventHandler, eventHandler,
eventListener, eventListener,
...@@ -86,10 +99,11 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer { ...@@ -86,10 +99,11 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
@Override @Override
protected int supportsFormatInternal(DrmSessionManager<ExoMediaCrypto> drmSessionManager, protected int supportsFormatInternal(DrmSessionManager<ExoMediaCrypto> drmSessionManager,
Format format) { Format format) {
String sampleMimeType = format.sampleMimeType; Assertions.checkNotNull(format.sampleMimeType);
if (!FfmpegLibrary.isAvailable() || !MimeTypes.isAudio(sampleMimeType)) { if (!FfmpegLibrary.isAvailable() || !MimeTypes.isAudio(format.sampleMimeType)) {
return FORMAT_UNSUPPORTED_TYPE; return FORMAT_UNSUPPORTED_TYPE;
} else if (!FfmpegLibrary.supportsFormat(sampleMimeType) || !isOutputSupported(format)) { } else if (!FfmpegLibrary.supportsFormat(format.sampleMimeType, format.pcmEncoding)
|| !isOutputSupported(format)) {
return FORMAT_UNSUPPORTED_SUBTYPE; return FORMAT_UNSUPPORTED_SUBTYPE;
} else if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) { } else if (!supportsFormatDrm(drmSessionManager, format.drmInitData)) {
return FORMAT_UNSUPPORTED_DRM; return FORMAT_UNSUPPORTED_DRM;
...@@ -106,18 +120,35 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer { ...@@ -106,18 +120,35 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
@Override @Override
protected FfmpegDecoder createDecoder(Format format, ExoMediaCrypto mediaCrypto) protected FfmpegDecoder createDecoder(Format format, ExoMediaCrypto mediaCrypto)
throws FfmpegDecoderException { throws FfmpegDecoderException {
decoder = new FfmpegDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE, decoder =
format.sampleMimeType, format.initializationData, shouldUseFloatOutput(format)); new FfmpegDecoder(
NUM_BUFFERS,
NUM_BUFFERS,
INITIAL_INPUT_BUFFER_SIZE,
format,
shouldUseFloatOutput(format));
return decoder; return decoder;
} }
@Override @Override
public Format getOutputFormat() { public Format getOutputFormat() {
Assertions.checkNotNull(decoder);
int channelCount = decoder.getChannelCount(); int channelCount = decoder.getChannelCount();
int sampleRate = decoder.getSampleRate(); int sampleRate = decoder.getSampleRate();
@C.PcmEncoding int encoding = decoder.getEncoding(); @C.PcmEncoding int encoding = decoder.getEncoding();
return Format.createAudioSampleFormat(null, MimeTypes.AUDIO_RAW, null, Format.NO_VALUE, return Format.createAudioSampleFormat(
Format.NO_VALUE, channelCount, sampleRate, encoding, null, null, 0, null); /* id= */ null,
MimeTypes.AUDIO_RAW,
/* codecs= */ null,
Format.NO_VALUE,
Format.NO_VALUE,
channelCount,
sampleRate,
encoding,
Collections.emptyList(),
/* drmInitData= */ null,
/* selectionFlags= */ 0,
/* language= */ null);
} }
private boolean isOutputSupported(Format inputFormat) { private boolean isOutputSupported(Format inputFormat) {
...@@ -125,6 +156,7 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer { ...@@ -125,6 +156,7 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
} }
private boolean shouldUseFloatOutput(Format inputFormat) { private boolean shouldUseFloatOutput(Format inputFormat) {
Assertions.checkNotNull(inputFormat.sampleMimeType);
if (!enableFloatOutput || !supportsOutputEncoding(C.ENCODING_PCM_FLOAT)) { if (!enableFloatOutput || !supportsOutputEncoding(C.ENCODING_PCM_FLOAT)) {
return false; return false;
} }
......
...@@ -15,10 +15,13 @@ ...@@ -15,10 +15,13 @@
*/ */
package com.google.android.exoplayer2.ext.ffmpeg; package com.google.android.exoplayer2.ext.ffmpeg;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; 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.DecoderInputBuffer;
import com.google.android.exoplayer2.decoder.SimpleDecoder; import com.google.android.exoplayer2.decoder.SimpleDecoder;
import com.google.android.exoplayer2.decoder.SimpleOutputBuffer; 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.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
...@@ -30,13 +33,12 @@ import java.util.List; ...@@ -30,13 +33,12 @@ import java.util.List;
/* package */ final class FfmpegDecoder extends /* package */ final class FfmpegDecoder extends
SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, FfmpegDecoderException> { SimpleDecoder<DecoderInputBuffer, SimpleOutputBuffer, FfmpegDecoderException> {
// Space for 64 ms of 48 kHz 8 channel 16-bit PCM audio. // Output buffer sizes when decoding PCM mu-law streams, which is the maximum FFmpeg outputs.
private static final int OUTPUT_BUFFER_SIZE_16BIT = 64 * 48 * 8 * 2; private static final int OUTPUT_BUFFER_SIZE_16BIT = 65536;
// Space for 64 ms of 48 KhZ 8 channel 32-bit PCM audio.
private static final int OUTPUT_BUFFER_SIZE_32BIT = OUTPUT_BUFFER_SIZE_16BIT * 2; private static final int OUTPUT_BUFFER_SIZE_32BIT = OUTPUT_BUFFER_SIZE_16BIT * 2;
private final String codecName; private final String codecName;
private final byte[] extraData; private final @Nullable byte[] extraData;
private final @C.Encoding int encoding; private final @C.Encoding int encoding;
private final int outputBufferSize; private final int outputBufferSize;
...@@ -45,18 +47,26 @@ import java.util.List; ...@@ -45,18 +47,26 @@ import java.util.List;
private volatile int channelCount; private volatile int channelCount;
private volatile int sampleRate; private volatile int sampleRate;
public FfmpegDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize, public FfmpegDecoder(
String mimeType, List<byte[]> initializationData, boolean outputFloat) int numInputBuffers,
int numOutputBuffers,
int initialInputBufferSize,
Format format,
boolean outputFloat)
throws FfmpegDecoderException { throws FfmpegDecoderException {
super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]); super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]);
if (!FfmpegLibrary.isAvailable()) { if (!FfmpegLibrary.isAvailable()) {
throw new FfmpegDecoderException("Failed to load decoder native libraries."); throw new FfmpegDecoderException("Failed to load decoder native libraries.");
} }
codecName = FfmpegLibrary.getCodecName(mimeType); Assertions.checkNotNull(format.sampleMimeType);
extraData = getExtraData(mimeType, initializationData); codecName =
Assertions.checkNotNull(
FfmpegLibrary.getCodecName(format.sampleMimeType, format.pcmEncoding));
extraData = getExtraData(format.sampleMimeType, format.initializationData);
encoding = outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT; encoding = outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT;
outputBufferSize = outputFloat ? OUTPUT_BUFFER_SIZE_32BIT : OUTPUT_BUFFER_SIZE_16BIT; outputBufferSize = outputFloat ? OUTPUT_BUFFER_SIZE_32BIT : OUTPUT_BUFFER_SIZE_16BIT;
nativeContext = ffmpegInitialize(codecName, extraData, outputFloat); nativeContext =
ffmpegInitialize(codecName, extraData, outputFloat, format.sampleRate, format.channelCount);
if (nativeContext == 0) { if (nativeContext == 0) {
throw new FfmpegDecoderException("Initialization failed."); throw new FfmpegDecoderException("Initialization failed.");
} }
...@@ -84,7 +94,7 @@ import java.util.List; ...@@ -84,7 +94,7 @@ import java.util.List;
} }
@Override @Override
protected FfmpegDecoderException decode( protected @Nullable FfmpegDecoderException decode(
DecoderInputBuffer inputBuffer, SimpleOutputBuffer outputBuffer, boolean reset) { DecoderInputBuffer inputBuffer, SimpleOutputBuffer outputBuffer, boolean reset) {
if (reset) { if (reset) {
nativeContext = ffmpegReset(nativeContext, extraData); nativeContext = ffmpegReset(nativeContext, extraData);
...@@ -103,6 +113,7 @@ import java.util.List; ...@@ -103,6 +113,7 @@ import java.util.List;
channelCount = ffmpegGetChannelCount(nativeContext); channelCount = ffmpegGetChannelCount(nativeContext);
sampleRate = ffmpegGetSampleRate(nativeContext); sampleRate = ffmpegGetSampleRate(nativeContext);
if (sampleRate == 0 && "alac".equals(codecName)) { if (sampleRate == 0 && "alac".equals(codecName)) {
Assertions.checkNotNull(extraData);
// ALAC decoder did not set the sample rate in earlier versions of FFMPEG. // ALAC decoder did not set the sample rate in earlier versions of FFMPEG.
// See https://trac.ffmpeg.org/ticket/6096 // See https://trac.ffmpeg.org/ticket/6096
ParsableByteArray parsableExtraData = new ParsableByteArray(extraData); ParsableByteArray parsableExtraData = new ParsableByteArray(extraData);
...@@ -148,7 +159,7 @@ import java.util.List; ...@@ -148,7 +159,7 @@ import java.util.List;
* Returns FFmpeg-compatible codec-specific initialization data ("extra data"), or {@code null} if * Returns FFmpeg-compatible codec-specific initialization data ("extra data"), or {@code null} if
* not required. * not required.
*/ */
private static byte[] getExtraData(String mimeType, List<byte[]> initializationData) { private static @Nullable byte[] getExtraData(String mimeType, List<byte[]> initializationData) {
switch (mimeType) { switch (mimeType) {
case MimeTypes.AUDIO_AAC: case MimeTypes.AUDIO_AAC:
case MimeTypes.AUDIO_ALAC: case MimeTypes.AUDIO_ALAC:
...@@ -173,12 +184,20 @@ import java.util.List; ...@@ -173,12 +184,20 @@ import java.util.List;
} }
} }
private native long ffmpegInitialize(String codecName, byte[] extraData, boolean outputFloat); 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, private native int ffmpegDecode(long context, ByteBuffer inputData, int inputSize,
ByteBuffer outputData, int outputSize); ByteBuffer outputData, int outputSize);
private native int ffmpegGetChannelCount(long context); private native int ffmpegGetChannelCount(long context);
private native int ffmpegGetSampleRate(long context); private native int ffmpegGetSampleRate(long context);
private native long ffmpegReset(long context, byte[] extraData);
private native long ffmpegReset(long context, @Nullable byte[] extraData);
private native void ffmpegRelease(long context); private native void ffmpegRelease(long context);
} }
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer2.ext.ffmpeg; package com.google.android.exoplayer2.ext.ffmpeg;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.util.LibraryLoader; import com.google.android.exoplayer2.util.LibraryLoader;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
...@@ -51,10 +53,8 @@ public final class FfmpegLibrary { ...@@ -51,10 +53,8 @@ public final class FfmpegLibrary {
return LOADER.isAvailable(); return LOADER.isAvailable();
} }
/** /** Returns the version of the underlying library if available, or null otherwise. */
* Returns the version of the underlying library if available, or null otherwise. public static @Nullable String getVersion() {
*/
public static String getVersion() {
return isAvailable() ? ffmpegGetVersion() : null; return isAvailable() ? ffmpegGetVersion() : null;
} }
...@@ -62,19 +62,21 @@ public final class FfmpegLibrary { ...@@ -62,19 +62,21 @@ public final class FfmpegLibrary {
* Returns whether the underlying library supports the specified MIME type. * Returns whether the underlying library supports the specified MIME type.
* *
* @param mimeType The MIME type to check. * @param mimeType The MIME type to check.
* @param encoding The PCM encoding for raw audio.
*/ */
public static boolean supportsFormat(String mimeType) { public static boolean supportsFormat(String mimeType, @C.PcmEncoding int encoding) {
if (!isAvailable()) { if (!isAvailable()) {
return false; return false;
} }
String codecName = getCodecName(mimeType); String codecName = getCodecName(mimeType, encoding);
return codecName != null && ffmpegHasDecoder(codecName); return codecName != null && ffmpegHasDecoder(codecName);
} }
/** /**
* Returns the name of the FFmpeg decoder that could be used to decode {@code mimeType}. * Returns the name of the FFmpeg decoder that could be used to decode the format, or {@code null}
* if it's unsupported.
*/ */
/* package */ static String getCodecName(String mimeType) { /* package */ static @Nullable String getCodecName(String mimeType, @C.PcmEncoding int encoding) {
switch (mimeType) { switch (mimeType) {
case MimeTypes.AUDIO_AAC: case MimeTypes.AUDIO_AAC:
return "aac"; return "aac";
...@@ -103,6 +105,14 @@ public final class FfmpegLibrary { ...@@ -103,6 +105,14 @@ public final class FfmpegLibrary {
return "flac"; return "flac";
case MimeTypes.AUDIO_ALAC: case MimeTypes.AUDIO_ALAC:
return "alac"; return "alac";
case MimeTypes.AUDIO_RAW:
if (encoding == C.ENCODING_PCM_MU_LAW) {
return "pcm_mulaw";
} else if (encoding == C.ENCODING_PCM_A_LAW) {
return "pcm_alaw";
} else {
return null;
}
default: default:
return null; return null;
} }
......
...@@ -27,6 +27,7 @@ extern "C" { ...@@ -27,6 +27,7 @@ extern "C" {
#endif #endif
#include <libavcodec/avcodec.h> #include <libavcodec/avcodec.h>
#include <libavresample/avresample.h> #include <libavresample/avresample.h>
#include <libavutil/channel_layout.h>
#include <libavutil/error.h> #include <libavutil/error.h>
#include <libavutil/opt.h> #include <libavutil/opt.h>
} }
...@@ -72,8 +73,9 @@ AVCodec *getCodecByName(JNIEnv* env, jstring codecName); ...@@ -72,8 +73,9 @@ AVCodec *getCodecByName(JNIEnv* env, jstring codecName);
* provided extraData as initialization data for the decoder if it is non-NULL. * provided extraData as initialization data for the decoder if it is non-NULL.
* Returns the created context. * Returns the created context.
*/ */
AVCodecContext *createContext(JNIEnv *env, AVCodec *codec, AVCodecContext *createContext(JNIEnv *env, AVCodec *codec, jbyteArray extraData,
jbyteArray extraData, jboolean outputFloat); jboolean outputFloat, jint rawSampleRate,
jint rawChannelCount);
/** /**
* Decodes the packet into the output buffer, returning the number of bytes * Decodes the packet into the output buffer, returning the number of bytes
...@@ -110,13 +112,14 @@ LIBRARY_FUNC(jboolean, ffmpegHasDecoder, jstring codecName) { ...@@ -110,13 +112,14 @@ LIBRARY_FUNC(jboolean, ffmpegHasDecoder, jstring codecName) {
} }
DECODER_FUNC(jlong, ffmpegInitialize, jstring codecName, jbyteArray extraData, DECODER_FUNC(jlong, ffmpegInitialize, jstring codecName, jbyteArray extraData,
jboolean outputFloat) { jboolean outputFloat, jint rawSampleRate, jint rawChannelCount) {
AVCodec *codec = getCodecByName(env, codecName); AVCodec *codec = getCodecByName(env, codecName);
if (!codec) { if (!codec) {
LOGE("Codec not found."); LOGE("Codec not found.");
return 0L; return 0L;
} }
return (jlong) createContext(env, codec, extraData, outputFloat); return (jlong)createContext(env, codec, extraData, outputFloat, rawSampleRate,
rawChannelCount);
} }
DECODER_FUNC(jint, ffmpegDecode, jlong context, jobject inputData, DECODER_FUNC(jint, ffmpegDecode, jlong context, jobject inputData,
...@@ -180,8 +183,11 @@ DECODER_FUNC(jlong, ffmpegReset, jlong jContext, jbyteArray extraData) { ...@@ -180,8 +183,11 @@ DECODER_FUNC(jlong, ffmpegReset, jlong jContext, jbyteArray extraData) {
LOGE("Unexpected error finding codec %d.", codecId); LOGE("Unexpected error finding codec %d.", codecId);
return 0L; return 0L;
} }
return (jlong) createContext(env, codec, extraData, jboolean outputFloat =
context->request_sample_fmt == OUTPUT_FORMAT_PCM_FLOAT); (jboolean)(context->request_sample_fmt == OUTPUT_FORMAT_PCM_FLOAT);
return (jlong)createContext(env, codec, extraData, outputFloat,
/* rawSampleRate= */ -1,
/* rawChannelCount= */ -1);
} }
avcodec_flush_buffers(context); avcodec_flush_buffers(context);
...@@ -204,8 +210,9 @@ AVCodec *getCodecByName(JNIEnv* env, jstring codecName) { ...@@ -204,8 +210,9 @@ AVCodec *getCodecByName(JNIEnv* env, jstring codecName) {
return codec; return codec;
} }
AVCodecContext *createContext(JNIEnv *env, AVCodec *codec, AVCodecContext *createContext(JNIEnv *env, AVCodec *codec, jbyteArray extraData,
jbyteArray extraData, jboolean outputFloat) { jboolean outputFloat, jint rawSampleRate,
jint rawChannelCount) {
AVCodecContext *context = avcodec_alloc_context3(codec); AVCodecContext *context = avcodec_alloc_context3(codec);
if (!context) { if (!context) {
LOGE("Failed to allocate context."); LOGE("Failed to allocate context.");
...@@ -225,6 +232,12 @@ AVCodecContext *createContext(JNIEnv *env, AVCodec *codec, ...@@ -225,6 +232,12 @@ AVCodecContext *createContext(JNIEnv *env, AVCodec *codec,
} }
env->GetByteArrayRegion(extraData, 0, size, (jbyte *) context->extradata); env->GetByteArrayRegion(extraData, 0, size, (jbyte *) context->extradata);
} }
if (context->codec_id == AV_CODEC_ID_PCM_MULAW ||
context->codec_id == AV_CODEC_ID_PCM_ALAW) {
context->sample_rate = rawSampleRate;
context->channels = rawChannelCount;
context->channel_layout = av_get_default_channel_layout(rawChannelCount);
}
int result = avcodec_open2(context, codec, NULL); int result = avcodec_open2(context, codec, NULL);
if (result < 0) { if (result < 0) {
logError("avcodec_open2", result); logError("avcodec_open2", result);
......
...@@ -136,6 +136,8 @@ public final class C { ...@@ -136,6 +136,8 @@ public final class C {
ENCODING_PCM_24BIT, ENCODING_PCM_24BIT,
ENCODING_PCM_32BIT, ENCODING_PCM_32BIT,
ENCODING_PCM_FLOAT, ENCODING_PCM_FLOAT,
ENCODING_PCM_MU_LAW,
ENCODING_PCM_A_LAW,
ENCODING_AC3, ENCODING_AC3,
ENCODING_E_AC3, ENCODING_E_AC3,
ENCODING_DTS, ENCODING_DTS,
...@@ -144,12 +146,19 @@ public final class C { ...@@ -144,12 +146,19 @@ public final class C {
}) })
public @interface Encoding {} public @interface Encoding {}
/** /** Represents a PCM audio encoding, or an invalid or unset value. */
* Represents a PCM audio encoding, or an invalid or unset value.
*/
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, @IntDef({
ENCODING_PCM_24BIT, ENCODING_PCM_32BIT, ENCODING_PCM_FLOAT}) Format.NO_VALUE,
ENCODING_INVALID,
ENCODING_PCM_8BIT,
ENCODING_PCM_16BIT,
ENCODING_PCM_24BIT,
ENCODING_PCM_32BIT,
ENCODING_PCM_FLOAT,
ENCODING_PCM_MU_LAW,
ENCODING_PCM_A_LAW
})
public @interface PcmEncoding {} public @interface PcmEncoding {}
/** @see AudioFormat#ENCODING_INVALID */ /** @see AudioFormat#ENCODING_INVALID */
public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID; public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID;
...@@ -163,6 +172,10 @@ public final class C { ...@@ -163,6 +172,10 @@ public final class C {
public static final int ENCODING_PCM_32BIT = 0x40000000; public static final int ENCODING_PCM_32BIT = 0x40000000;
/** @see AudioFormat#ENCODING_PCM_FLOAT */ /** @see AudioFormat#ENCODING_PCM_FLOAT */
public static final int ENCODING_PCM_FLOAT = AudioFormat.ENCODING_PCM_FLOAT; public static final int ENCODING_PCM_FLOAT = AudioFormat.ENCODING_PCM_FLOAT;
/** Audio encoding for mu-law. */
public static final int ENCODING_PCM_MU_LAW = 0x10000000;
/** Audio encoding for A-law. */
public static final int ENCODING_PCM_A_LAW = 0x20000000;
/** @see AudioFormat#ENCODING_AC3 */ /** @see AudioFormat#ENCODING_AC3 */
public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3; public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3;
/** @see AudioFormat#ENCODING_E_AC3 */ /** @see AudioFormat#ENCODING_E_AC3 */
......
...@@ -125,12 +125,12 @@ public final class Format implements Parcelable { ...@@ -125,12 +125,12 @@ public final class Format implements Parcelable {
public final int sampleRate; public final int sampleRate;
/** /**
* The encoding for PCM audio streams. If {@link #sampleMimeType} is {@link MimeTypes#AUDIO_RAW} * The encoding for PCM audio streams. If {@link #sampleMimeType} is {@link MimeTypes#AUDIO_RAW}
* then one of {@link C#ENCODING_PCM_8BIT}, {@link C#ENCODING_PCM_16BIT}, * then one of {@link C#ENCODING_PCM_8BIT}, {@link C#ENCODING_PCM_16BIT}, {@link
* {@link C#ENCODING_PCM_24BIT} and {@link C#ENCODING_PCM_32BIT}. Set to {@link #NO_VALUE} for * C#ENCODING_PCM_24BIT}, {@link C#ENCODING_PCM_32BIT}, {@link C#ENCODING_PCM_FLOAT}, {@link
* other media types. * C#ENCODING_PCM_MU_LAW} or {@link C#ENCODING_PCM_A_LAW}. Set to {@link #NO_VALUE} for other
* media types.
*/ */
@C.PcmEncoding public final @C.PcmEncoding int pcmEncoding;
public final int pcmEncoding;
/** /**
* The number of frames to trim from the start of the decoded audio stream, or 0 if not * The number of frames to trim from the start of the decoded audio stream, or 0 if not
* applicable. * applicable.
......
...@@ -193,7 +193,7 @@ import java.lang.reflect.Method; ...@@ -193,7 +193,7 @@ import java.lang.reflect.Method;
audioTimestampPoller = new AudioTimestampPoller(audioTrack); audioTimestampPoller = new AudioTimestampPoller(audioTrack);
outputSampleRate = audioTrack.getSampleRate(); outputSampleRate = audioTrack.getSampleRate();
needsPassthroughWorkarounds = needsPassthroughWorkarounds(outputEncoding); needsPassthroughWorkarounds = needsPassthroughWorkarounds(outputEncoding);
isOutputPcm = Util.isEncodingPcm(outputEncoding); isOutputPcm = Util.isEncodingLinearPcm(outputEncoding);
bufferSizeUs = isOutputPcm ? framesToDurationUs(bufferSize / outputPcmFrameSize) : C.TIME_UNSET; bufferSizeUs = isOutputPcm ? framesToDurationUs(bufferSize / outputPcmFrameSize) : C.TIME_UNSET;
lastRawPlaybackHeadPosition = 0; lastRawPlaybackHeadPosition = 0;
rawPlaybackHeadWrapCount = 0; rawPlaybackHeadWrapCount = 0;
......
...@@ -371,7 +371,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -371,7 +371,7 @@ public final class DefaultAudioSink implements AudioSink {
@Override @Override
public boolean isEncodingSupported(@C.Encoding int encoding) { public boolean isEncodingSupported(@C.Encoding int encoding) {
if (Util.isEncodingPcm(encoding)) { if (Util.isEncodingLinearPcm(encoding)) {
// AudioTrack supports 16-bit integer PCM output in all platform API versions, and float // AudioTrack supports 16-bit integer PCM output in all platform API versions, and float
// output from platform API version 21 only. Other integer PCM encodings are resampled by this // output from platform API version 21 only. Other integer PCM encodings are resampled by this
// sink to 16-bit PCM. // sink to 16-bit PCM.
...@@ -405,7 +405,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -405,7 +405,7 @@ public final class DefaultAudioSink implements AudioSink {
this.inputSampleRate = inputSampleRate; this.inputSampleRate = inputSampleRate;
int channelCount = inputChannelCount; int channelCount = inputChannelCount;
int sampleRate = inputSampleRate; int sampleRate = inputSampleRate;
isInputPcm = Util.isEncodingPcm(inputEncoding); isInputPcm = Util.isEncodingLinearPcm(inputEncoding);
shouldConvertHighResIntPcmToFloat = shouldConvertHighResIntPcmToFloat =
enableConvertHighResIntPcmToFloat enableConvertHighResIntPcmToFloat
&& isEncodingSupported(C.ENCODING_PCM_32BIT) && isEncodingSupported(C.ENCODING_PCM_32BIT)
......
...@@ -20,6 +20,7 @@ import android.os.Handler; ...@@ -20,6 +20,7 @@ import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.BaseRenderer;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
...@@ -121,7 +122,9 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -121,7 +122,9 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
* @param eventListener A listener of events. May be null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output. * @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output.
*/ */
public SimpleDecoderAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener, public SimpleDecoderAudioRenderer(
@Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener,
AudioProcessor... audioProcessors) { AudioProcessor... audioProcessors) {
this( this(
eventHandler, eventHandler,
...@@ -139,8 +142,10 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -139,8 +142,10 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
* @param audioCapabilities The audio capabilities for playback on this device. May be null if the * @param audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed. * default capabilities (no encoded audio passthrough support) should be assumed.
*/ */
public SimpleDecoderAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener, public SimpleDecoderAudioRenderer(
AudioCapabilities audioCapabilities) { @Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener,
@Nullable AudioCapabilities audioCapabilities) {
this( this(
eventHandler, eventHandler,
eventListener, eventListener,
...@@ -164,9 +169,13 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -164,9 +169,13 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
* has obtained the keys necessary to decrypt encrypted regions of the media. * has obtained the keys necessary to decrypt encrypted regions of the media.
* @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output. * @param audioProcessors Optional {@link AudioProcessor}s that will process audio before output.
*/ */
public SimpleDecoderAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener, public SimpleDecoderAudioRenderer(
AudioCapabilities audioCapabilities, DrmSessionManager<ExoMediaCrypto> drmSessionManager, @Nullable Handler eventHandler,
boolean playClearSamplesWithoutKeys, AudioProcessor... audioProcessors) { @Nullable AudioRendererEventListener eventListener,
@Nullable AudioCapabilities audioCapabilities,
@Nullable DrmSessionManager<ExoMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys,
AudioProcessor... audioProcessors) {
this(eventHandler, eventListener, drmSessionManager, this(eventHandler, eventListener, drmSessionManager,
playClearSamplesWithoutKeys, new DefaultAudioSink(audioCapabilities, audioProcessors)); playClearSamplesWithoutKeys, new DefaultAudioSink(audioCapabilities, audioProcessors));
} }
...@@ -184,8 +193,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -184,8 +193,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
* has obtained the keys necessary to decrypt encrypted regions of the media. * has obtained the keys necessary to decrypt encrypted regions of the media.
* @param audioSink The sink to which audio will be output. * @param audioSink The sink to which audio will be output.
*/ */
public SimpleDecoderAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener, public SimpleDecoderAudioRenderer(
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys, @Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener,
@Nullable DrmSessionManager<ExoMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys,
AudioSink audioSink) { AudioSink audioSink) {
super(C.TRACK_TYPE_AUDIO); super(C.TRACK_TYPE_AUDIO);
this.drmSessionManager = drmSessionManager; this.drmSessionManager = drmSessionManager;
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.decoder; package com.google.android.exoplayer2.decoder;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.util.ArrayDeque; import java.util.ArrayDeque;
...@@ -292,14 +293,13 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp ...@@ -292,14 +293,13 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
* Decodes the {@code inputBuffer} and stores any decoded output in {@code outputBuffer}. * Decodes the {@code inputBuffer} and stores any decoded output in {@code outputBuffer}.
* *
* @param inputBuffer The buffer to decode. * @param inputBuffer The buffer to decode.
* @param outputBuffer The output buffer to store decoded data. The flag * @param outputBuffer The output buffer to store decoded data. The flag {@link
* {@link C#BUFFER_FLAG_DECODE_ONLY} will be set if the same flag is set on * C#BUFFER_FLAG_DECODE_ONLY} will be set if the same flag is set on {@code inputBuffer}, but
* {@code inputBuffer}, but may be set/unset as required. If the flag is set when the call * may be set/unset as required. If the flag is set when the call returns then the output
* returns then the output buffer will not be made available to dequeue. The output buffer * buffer will not be made available to dequeue. The output buffer may not have been populated
* may not have been populated in this case. * in this case.
* @param reset Whether the decoder must be reset before decoding. * @param reset Whether the decoder must be reset before decoding.
* @return A decoder exception if an error occurred, or null if decoding was successful. * @return A decoder exception if an error occurred, or null if decoding was successful.
*/ */
protected abstract E decode(I inputBuffer, O outputBuffer, boolean reset); protected abstract @Nullable E decode(I inputBuffer, O outputBuffer, boolean reset);
} }
...@@ -33,6 +33,10 @@ import java.io.IOException; ...@@ -33,6 +33,10 @@ import java.io.IOException;
private static final int TYPE_PCM = 0x0001; private static final int TYPE_PCM = 0x0001;
/** Float PCM audio data. */ /** Float PCM audio data. */
private static final int TYPE_FLOAT = 0x0003; private static final int TYPE_FLOAT = 0x0003;
/** 8-bit ITU-T G.711 A-law audio data. */
private static final int TYPE_A_LAW = 0x0006;
/** 8-bit ITU-T G.711 mu-law audio data. */
private static final int TYPE_MU_LAW = 0x0007;
/** Extended WAVE format. */ /** Extended WAVE format. */
private static final int TYPE_WAVE_FORMAT_EXTENSIBLE = 0xFFFE; private static final int TYPE_WAVE_FORMAT_EXTENSIBLE = 0xFFFE;
...@@ -98,6 +102,12 @@ import java.io.IOException; ...@@ -98,6 +102,12 @@ import java.io.IOException;
case TYPE_FLOAT: case TYPE_FLOAT:
encoding = bitsPerSample == 32 ? C.ENCODING_PCM_FLOAT : C.ENCODING_INVALID; encoding = bitsPerSample == 32 ? C.ENCODING_PCM_FLOAT : C.ENCODING_INVALID;
break; break;
case TYPE_A_LAW:
encoding = C.ENCODING_PCM_A_LAW;
break;
case TYPE_MU_LAW:
encoding = C.ENCODING_PCM_MU_LAW;
break;
default: default:
Log.e(TAG, "Unsupported WAV format type: " + type); Log.e(TAG, "Unsupported WAV format type: " + type);
return null; return null;
......
...@@ -1092,12 +1092,12 @@ public final class Util { ...@@ -1092,12 +1092,12 @@ public final class Util {
} }
/** /**
* Returns whether {@code encoding} is one of the PCM encodings. * Returns whether {@code encoding} is one of the linear PCM encodings.
* *
* @param encoding The encoding of the audio data. * @param encoding The encoding of the audio data.
* @return Whether the encoding is one of the PCM encodings. * @return Whether the encoding is one of the PCM encodings.
*/ */
public static boolean isEncodingPcm(@C.Encoding int encoding) { public static boolean isEncodingLinearPcm(@C.Encoding int encoding) {
return encoding == C.ENCODING_PCM_8BIT return encoding == C.ENCODING_PCM_8BIT
|| encoding == C.ENCODING_PCM_16BIT || encoding == C.ENCODING_PCM_16BIT
|| encoding == C.ENCODING_PCM_24BIT || encoding == C.ENCODING_PCM_24BIT
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment