Skip to content

Commit fe4bd76

Browse files
committed
ffmpeg: reallocate output buffer dynamically
With FFmpeg we can't determine size of output buffer ahead of time for all codecs, so we need to reallocate it when needed instead of simply failing.
1 parent beb1711 commit fe4bd76

File tree

3 files changed

+82
-19
lines changed

3 files changed

+82
-19
lines changed

libraries/decoder/src/main/java/androidx/media3/decoder/SimpleDecoderOutputBuffer.java

+13
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package androidx.media3.decoder;
1717

1818
import androidx.annotation.Nullable;
19+
import androidx.media3.common.util.Assertions;
1920
import androidx.media3.common.util.UnstableApi;
2021
import java.nio.ByteBuffer;
2122
import java.nio.ByteOrder;
@@ -49,6 +50,18 @@ public ByteBuffer init(long timeUs, int size) {
4950
return data;
5051
}
5152

53+
public ByteBuffer grow(int newSize) {
54+
Assertions.checkNotNull(data);
55+
final ByteBuffer newData = ByteBuffer.allocateDirect(newSize).order(ByteOrder.nativeOrder());
56+
final int restorePosition = data.position();
57+
data.position(0);
58+
newData.put(data);
59+
newData.position(restorePosition);
60+
newData.limit(newSize);
61+
data = newData;
62+
return newData;
63+
}
64+
5265
@Override
5366
public void clear() {
5467
super.clear();

libraries/decoder_ffmpeg/src/main/java/androidx/media3/decoder/ffmpeg/FfmpegAudioDecoder.java

+22-10
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package androidx.media3.decoder.ffmpeg;
1717

18+
import androidx.annotation.Keep;
1819
import androidx.annotation.Nullable;
1920
import androidx.media3.common.C;
2021
import androidx.media3.common.Format;
@@ -27,22 +28,23 @@
2728
import androidx.media3.decoder.SimpleDecoderOutputBuffer;
2829
import java.nio.ByteBuffer;
2930
import java.util.List;
31+
import java.util.function.Function;
32+
import java.util.function.IntConsumer;
3033

3134
/** FFmpeg audio decoder. */
3235
/* package */ final class FfmpegAudioDecoder
3336
extends SimpleDecoder<DecoderInputBuffer, SimpleDecoderOutputBuffer, FfmpegDecoderException> {
3437

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+
private static final int INITIAL_OUTPUT_BUFFER_SIZE_16BIT = 65535;
39+
private static final int INITIAL_OUTPUT_BUFFER_SIZE_32BIT = INITIAL_OUTPUT_BUFFER_SIZE_16BIT * 2;
3840

3941
private static final int AUDIO_DECODER_ERROR_INVALID_DATA = -1;
4042
private static final int AUDIO_DECODER_ERROR_OTHER = -2;
4143

4244
private final String codecName;
4345
@Nullable private final byte[] extraData;
4446
private final @C.PcmEncoding int encoding;
45-
private final int outputBufferSize;
47+
private int outputBufferSize;
4648

4749
private long nativeContext; // May be reassigned on resetting the codec.
4850
private boolean hasOutputFormat;
@@ -64,7 +66,7 @@ public FfmpegAudioDecoder(
6466
codecName = Assertions.checkNotNull(FfmpegLibrary.getCodecName(format.sampleMimeType));
6567
extraData = getExtraData(format.sampleMimeType, format.initializationData);
6668
encoding = outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT;
67-
outputBufferSize = outputFloat ? OUTPUT_BUFFER_SIZE_32BIT : OUTPUT_BUFFER_SIZE_16BIT;
69+
outputBufferSize = outputFloat ? INITIAL_OUTPUT_BUFFER_SIZE_32BIT : INITIAL_OUTPUT_BUFFER_SIZE_16BIT;
6870
nativeContext =
6971
ffmpegInitialize(codecName, extraData, outputFloat, format.sampleRate, format.channelCount);
7072
if (nativeContext == 0) {
@@ -107,8 +109,10 @@ protected FfmpegDecoderException decode(
107109
}
108110
ByteBuffer inputData = Util.castNonNull(inputBuffer.data);
109111
int inputSize = inputData.limit();
110-
ByteBuffer outputData = outputBuffer.init(inputBuffer.timeUs, outputBufferSize);
111-
int result = ffmpegDecode(nativeContext, inputData, inputSize, outputData, outputBufferSize);
112+
outputBuffer.init(inputBuffer.timeUs, outputBufferSize);
113+
Assertions.checkNotNull(outputBuffer.data);
114+
115+
int result = ffmpegDecode(nativeContext, inputData, inputSize, outputBuffer, outputBuffer.data, outputBufferSize);
112116
if (result == AUDIO_DECODER_ERROR_OTHER) {
113117
return new FfmpegDecoderException("Error decoding (see logcat).");
114118
} else if (result == AUDIO_DECODER_ERROR_INVALID_DATA) {
@@ -135,11 +139,19 @@ protected FfmpegDecoderException decode(
135139
}
136140
hasOutputFormat = true;
137141
}
138-
outputData.position(0);
139-
outputData.limit(result);
142+
outputBuffer.data.position(0);
143+
outputBuffer.data.limit(result);
140144
return null;
141145
}
142146

147+
// Called from native code
148+
@Keep
149+
private ByteBuffer growOutputBuffer(SimpleDecoderOutputBuffer outputBuffer, int requiredSize) {
150+
// Use it for new buffer so that hopefully we won't need to reallocate again
151+
outputBufferSize = requiredSize;
152+
return outputBuffer.grow(requiredSize);
153+
}
154+
143155
@Override
144156
public void release() {
145157
super.release();
@@ -221,7 +233,7 @@ private native long ffmpegInitialize(
221233
int rawChannelCount);
222234

223235
private native int ffmpegDecode(
224-
long context, ByteBuffer inputData, int inputSize, ByteBuffer outputData, int outputSize);
236+
long context, ByteBuffer inputData, int inputSize, SimpleDecoderOutputBuffer decoderOutputBuffer, ByteBuffer outputData, int outputSize);
225237

226238
private native int ffmpegGetChannelCount(long context);
227239

libraries/decoder_ffmpeg/src/main/jni/ffmpeg_jni.cc

+47-9
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ static const AVSampleFormat OUTPUT_FORMAT_PCM_FLOAT = AV_SAMPLE_FMT_FLT;
6767
static const int AUDIO_DECODER_ERROR_INVALID_DATA = -1;
6868
static const int AUDIO_DECODER_ERROR_OTHER = -2;
6969

70+
static jmethodID growOutputBufferMethod;
71+
7072
/**
7173
* Returns the AVCodec with the specified name, or NULL if it is not available.
7274
*/
@@ -81,13 +83,21 @@ AVCodecContext *createContext(JNIEnv *env, AVCodec *codec, jbyteArray extraData,
8183
jboolean outputFloat, jint rawSampleRate,
8284
jint rawChannelCount);
8385

86+
struct GrowOutputBufferCallback {
87+
uint8_t *operator()(int requiredSize) const;
88+
89+
JNIEnv *env;
90+
jobject thiz;
91+
jobject decoderOutputBuffer;
92+
};
93+
8494
/**
8595
* Decodes the packet into the output buffer, returning the number of bytes
8696
* written, or a negative AUDIO_DECODER_ERROR constant value in the case of an
8797
* error.
8898
*/
8999
int decodePacket(AVCodecContext *context, AVPacket *packet,
90-
uint8_t *outputBuffer, int outputSize);
100+
uint8_t *outputBuffer, int outputSize, GrowOutputBufferCallback growBuffer);
91101

92102
/**
93103
* Transforms ffmpeg AVERROR into a negative AUDIO_DECODER_ERROR constant value.
@@ -107,6 +117,17 @@ void releaseContext(AVCodecContext *context);
107117
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
108118
JNIEnv *env;
109119
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
120+
LOGE("JNI_OnLoad: GetEnv failed");
121+
return -1;
122+
}
123+
jclass clazz = env->FindClass("androidx/media3/decoder/ffmpeg/FfmpegAudioDecoder");
124+
if (!clazz) {
125+
LOGE("JNI_OnLoad: FindClass failed");
126+
return -1;
127+
}
128+
growOutputBufferMethod = env->GetMethodID(clazz, "growOutputBuffer","(Landroidx/media3/decoder/SimpleDecoderOutputBuffer;I)Ljava/nio/ByteBuffer;");
129+
if (!growOutputBufferMethod) {
130+
LOGE("JNI_OnLoad: GetMethodID failed");
110131
return -1;
111132
}
112133
avcodec_register_all();
@@ -138,12 +159,12 @@ AUDIO_DECODER_FUNC(jlong, ffmpegInitialize, jstring codecName,
138159
}
139160

140161
AUDIO_DECODER_FUNC(jint, ffmpegDecode, jlong context, jobject inputData,
141-
jint inputSize, jobject outputData, jint outputSize) {
162+
jint inputSize, jobject decoderOutputBuffer, jobject outputData, jint outputSize) {
142163
if (!context) {
143164
LOGE("Context must be non-NULL.");
144165
return -1;
145166
}
146-
if (!inputData || !outputData) {
167+
if (!inputData || !decoderOutputBuffer || !outputData) {
147168
LOGE("Input and output buffers must be non-NULL.");
148169
return -1;
149170
}
@@ -162,7 +183,17 @@ AUDIO_DECODER_FUNC(jint, ffmpegDecode, jlong context, jobject inputData,
162183
packet.data = inputBuffer;
163184
packet.size = inputSize;
164185
return decodePacket((AVCodecContext *)context, &packet, outputBuffer,
165-
outputSize);
186+
outputSize, GrowOutputBufferCallback{env, thiz, decoderOutputBuffer});
187+
}
188+
189+
uint8_t *GrowOutputBufferCallback::operator()(int requiredSize) const {
190+
jobject newOutputData = env->CallObjectMethod(thiz, growOutputBufferMethod, decoderOutputBuffer, requiredSize);
191+
if (env->ExceptionCheck()) {
192+
LOGE("growOutputBuffer() failed");
193+
env->ExceptionDescribe();
194+
return nullptr;
195+
}
196+
return static_cast<uint8_t *>(env->GetDirectBufferAddress(newOutputData));
166197
}
167198

168199
AUDIO_DECODER_FUNC(jint, ffmpegGetChannelCount, jlong context) {
@@ -264,7 +295,7 @@ AVCodecContext *createContext(JNIEnv *env, AVCodec *codec, jbyteArray extraData,
264295
}
265296

266297
int decodePacket(AVCodecContext *context, AVPacket *packet,
267-
uint8_t *outputBuffer, int outputSize) {
298+
uint8_t *outputBuffer, int outputSize, GrowOutputBufferCallback growBuffer) {
268299
int result = 0;
269300
// Queue input data.
270301
result = avcodec_send_packet(context, packet);
@@ -320,15 +351,22 @@ int decodePacket(AVCodecContext *context, AVPacket *packet,
320351
}
321352
context->opaque = resampleContext;
322353
}
323-
int inSampleSize = av_get_bytes_per_sample(sampleFormat);
354+
324355
int outSampleSize = av_get_bytes_per_sample(context->request_sample_fmt);
325356
int outSamples = swr_get_out_samples(resampleContext, sampleCount);
326357
int bufferOutSize = outSampleSize * channelCount * outSamples;
327358
if (outSize + bufferOutSize > outputSize) {
328-
LOGE("Output buffer size (%d) too small for output data (%d).",
359+
LOGE("Output buffer size (%d) too small for output data (%d), reallocating buffer.",
329360
outputSize, outSize + bufferOutSize);
330-
av_frame_free(&frame);
331-
return AUDIO_DECODER_ERROR_INVALID_DATA;
361+
outputSize = outSize + bufferOutSize;
362+
outputBuffer = growBuffer(outputSize);
363+
if (outputBuffer) {
364+
LOGE("Reallocated output buffer.");
365+
} else {
366+
LOGE("Failed to reallocate output buffer.");
367+
av_frame_free(&frame);
368+
return AUDIO_DECODER_ERROR_OTHER;
369+
}
332370
}
333371
result = swr_convert(resampleContext, &outputBuffer, bufferOutSize,
334372
(const uint8_t **)frame->data, frame->nb_samples);

0 commit comments

Comments
 (0)