Skip to content

Commit 4f99000

Browse files
Merge pull request #746 from equeim:ffmpeg-output-buffer-reallocation
PiperOrigin-RevId: 578217064
2 parents 8d87a27 + 58d8850 commit 4f99000

File tree

4 files changed

+108
-17
lines changed

4 files changed

+108
-17
lines changed

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

+22
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,27 @@ public ByteBuffer init(long timeUs, int size) {
4950
return data;
5051
}
5152

53+
/**
54+
* Grows the buffer to a new size.
55+
*
56+
* <p>Existing data is copied to the new buffer, and {@link ByteBuffer#position} is preserved.
57+
*
58+
* @param newSize New size of the buffer.
59+
* @return The {@link #data} buffer, for convenience.
60+
*/
61+
public ByteBuffer grow(int newSize) {
62+
ByteBuffer oldData = Assertions.checkNotNull(this.data);
63+
Assertions.checkArgument(newSize >= oldData.limit());
64+
ByteBuffer newData = ByteBuffer.allocateDirect(newSize).order(ByteOrder.nativeOrder());
65+
int restorePosition = oldData.position();
66+
oldData.position(0);
67+
newData.put(oldData);
68+
newData.position(restorePosition);
69+
newData.limit(newSize);
70+
this.data = newData;
71+
return newData;
72+
}
73+
5274
@Override
5375
public void clear() {
5476
super.clear();

libraries/decoder_ffmpeg/proguard-rules.txt

+5
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@
44
-keepclasseswithmembernames class * {
55
native <methods>;
66
}
7+
8+
# This method is called from native code
9+
-keep class androidx.media3.decoder.ffmpeg.FfmpegAudioDecoder {
10+
private java.nio.ByteBuffer growOutputBuffer(androidx.media3.decoder.SimpleDecoderOutputBuffer, int);
11+
}

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

+22-7
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,16 @@
3232
/* package */ final class FfmpegAudioDecoder
3333
extends SimpleDecoder<DecoderInputBuffer, SimpleDecoderOutputBuffer, FfmpegDecoderException> {
3434

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

3938
private static final int AUDIO_DECODER_ERROR_INVALID_DATA = -1;
4039
private static final int AUDIO_DECODER_ERROR_OTHER = -2;
4140

4241
private final String codecName;
4342
@Nullable private final byte[] extraData;
4443
private final @C.PcmEncoding int encoding;
45-
private final int outputBufferSize;
44+
private int outputBufferSize;
4645

4746
private long nativeContext; // May be reassigned on resetting the codec.
4847
private boolean hasOutputFormat;
@@ -64,7 +63,8 @@ public FfmpegAudioDecoder(
6463
codecName = Assertions.checkNotNull(FfmpegLibrary.getCodecName(format.sampleMimeType));
6564
extraData = getExtraData(format.sampleMimeType, format.initializationData);
6665
encoding = outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT;
67-
outputBufferSize = outputFloat ? OUTPUT_BUFFER_SIZE_32BIT : OUTPUT_BUFFER_SIZE_16BIT;
66+
outputBufferSize =
67+
outputFloat ? INITIAL_OUTPUT_BUFFER_SIZE_32BIT : INITIAL_OUTPUT_BUFFER_SIZE_16BIT;
6868
nativeContext =
6969
ffmpegInitialize(codecName, extraData, outputFloat, format.sampleRate, format.channelCount);
7070
if (nativeContext == 0) {
@@ -108,7 +108,9 @@ protected FfmpegDecoderException decode(
108108
ByteBuffer inputData = Util.castNonNull(inputBuffer.data);
109109
int inputSize = inputData.limit();
110110
ByteBuffer outputData = outputBuffer.init(inputBuffer.timeUs, outputBufferSize);
111-
int result = ffmpegDecode(nativeContext, inputData, inputSize, outputData, outputBufferSize);
111+
int result =
112+
ffmpegDecode(
113+
nativeContext, inputData, inputSize, outputBuffer, outputData, outputBufferSize);
112114
if (result == AUDIO_DECODER_ERROR_OTHER) {
113115
return new FfmpegDecoderException("Error decoding (see logcat).");
114116
} else if (result == AUDIO_DECODER_ERROR_INVALID_DATA) {
@@ -140,6 +142,14 @@ protected FfmpegDecoderException decode(
140142
return null;
141143
}
142144

145+
// Called from native code
146+
@SuppressWarnings("unused")
147+
private ByteBuffer growOutputBuffer(SimpleDecoderOutputBuffer outputBuffer, int requiredSize) {
148+
// Use it for new buffer so that hopefully we won't need to reallocate again
149+
outputBufferSize = requiredSize;
150+
return outputBuffer.grow(requiredSize);
151+
}
152+
143153
@Override
144154
public void release() {
145155
super.release();
@@ -221,7 +231,12 @@ private native long ffmpegInitialize(
221231
int rawChannelCount);
222232

223233
private native int ffmpegDecode(
224-
long context, ByteBuffer inputData, int inputSize, ByteBuffer outputData, int outputSize);
234+
long context,
235+
ByteBuffer inputData,
236+
int inputSize,
237+
SimpleDecoderOutputBuffer decoderOutputBuffer,
238+
ByteBuffer outputData,
239+
int outputSize);
225240

226241
private native int ffmpegGetChannelCount(long context);
227242

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

+59-10
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ extern "C" {
3535
#define LOG_TAG "ffmpeg_jni"
3636
#define LOGE(...) \
3737
((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
38+
#define LOGD(...) \
39+
((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
3840

3941
#define LIBRARY_FUNC(RETURN_TYPE, NAME, ...) \
4042
extern "C" { \
@@ -67,6 +69,8 @@ static const AVSampleFormat OUTPUT_FORMAT_PCM_FLOAT = AV_SAMPLE_FMT_FLT;
6769
static const int AUDIO_DECODER_ERROR_INVALID_DATA = -1;
6870
static const int AUDIO_DECODER_ERROR_OTHER = -2;
6971

72+
static jmethodID growOutputBufferMethod;
73+
7074
/**
7175
* Returns the AVCodec with the specified name, or NULL if it is not available.
7276
*/
@@ -81,13 +85,22 @@ AVCodecContext *createContext(JNIEnv *env, AVCodec *codec, jbyteArray extraData,
8185
jboolean outputFloat, jint rawSampleRate,
8286
jint rawChannelCount);
8387

88+
struct GrowOutputBufferCallback {
89+
uint8_t *operator()(int requiredSize) const;
90+
91+
JNIEnv *env;
92+
jobject thiz;
93+
jobject decoderOutputBuffer;
94+
};
95+
8496
/**
8597
* Decodes the packet into the output buffer, returning the number of bytes
8698
* written, or a negative AUDIO_DECODER_ERROR constant value in the case of an
8799
* error.
88100
*/
89101
int decodePacket(AVCodecContext *context, AVPacket *packet,
90-
uint8_t *outputBuffer, int outputSize);
102+
uint8_t *outputBuffer, int outputSize,
103+
GrowOutputBufferCallback growBuffer);
91104

92105
/**
93106
* Transforms ffmpeg AVERROR into a negative AUDIO_DECODER_ERROR constant value.
@@ -107,6 +120,21 @@ void releaseContext(AVCodecContext *context);
107120
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
108121
JNIEnv *env;
109122
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
123+
LOGE("JNI_OnLoad: GetEnv failed");
124+
return -1;
125+
}
126+
jclass clazz =
127+
env->FindClass("androidx/media3/decoder/ffmpeg/FfmpegAudioDecoder");
128+
if (!clazz) {
129+
LOGE("JNI_OnLoad: FindClass failed");
130+
return -1;
131+
}
132+
growOutputBufferMethod =
133+
env->GetMethodID(clazz, "growOutputBuffer",
134+
"(Landroidx/media3/decoder/"
135+
"SimpleDecoderOutputBuffer;I)Ljava/nio/ByteBuffer;");
136+
if (!growOutputBufferMethod) {
137+
LOGE("JNI_OnLoad: GetMethodID failed");
110138
return -1;
111139
}
112140
avcodec_register_all();
@@ -138,12 +166,13 @@ AUDIO_DECODER_FUNC(jlong, ffmpegInitialize, jstring codecName,
138166
}
139167

140168
AUDIO_DECODER_FUNC(jint, ffmpegDecode, jlong context, jobject inputData,
141-
jint inputSize, jobject outputData, jint outputSize) {
169+
jint inputSize, jobject decoderOutputBuffer,
170+
jobject outputData, jint outputSize) {
142171
if (!context) {
143172
LOGE("Context must be non-NULL.");
144173
return -1;
145174
}
146-
if (!inputData || !outputData) {
175+
if (!inputData || !decoderOutputBuffer || !outputData) {
147176
LOGE("Input and output buffers must be non-NULL.");
148177
return -1;
149178
}
@@ -162,7 +191,19 @@ AUDIO_DECODER_FUNC(jint, ffmpegDecode, jlong context, jobject inputData,
162191
packet.data = inputBuffer;
163192
packet.size = inputSize;
164193
return decodePacket((AVCodecContext *)context, &packet, outputBuffer,
165-
outputSize);
194+
outputSize,
195+
GrowOutputBufferCallback{env, thiz, decoderOutputBuffer});
196+
}
197+
198+
uint8_t *GrowOutputBufferCallback::operator()(int requiredSize) const {
199+
jobject newOutputData = env->CallObjectMethod(
200+
thiz, growOutputBufferMethod, decoderOutputBuffer, requiredSize);
201+
if (env->ExceptionCheck()) {
202+
LOGE("growOutputBuffer() failed");
203+
env->ExceptionDescribe();
204+
return nullptr;
205+
}
206+
return static_cast<uint8_t *>(env->GetDirectBufferAddress(newOutputData));
166207
}
167208

168209
AUDIO_DECODER_FUNC(jint, ffmpegGetChannelCount, jlong context) {
@@ -264,7 +305,8 @@ AVCodecContext *createContext(JNIEnv *env, AVCodec *codec, jbyteArray extraData,
264305
}
265306

266307
int decodePacket(AVCodecContext *context, AVPacket *packet,
267-
uint8_t *outputBuffer, int outputSize) {
308+
uint8_t *outputBuffer, int outputSize,
309+
GrowOutputBufferCallback growBuffer) {
268310
int result = 0;
269311
// Queue input data.
270312
result = avcodec_send_packet(context, packet);
@@ -320,15 +362,22 @@ int decodePacket(AVCodecContext *context, AVPacket *packet,
320362
}
321363
context->opaque = resampleContext;
322364
}
323-
int inSampleSize = av_get_bytes_per_sample(sampleFormat);
365+
324366
int outSampleSize = av_get_bytes_per_sample(context->request_sample_fmt);
325367
int outSamples = swr_get_out_samples(resampleContext, sampleCount);
326368
int bufferOutSize = outSampleSize * channelCount * outSamples;
327369
if (outSize + bufferOutSize > outputSize) {
328-
LOGE("Output buffer size (%d) too small for output data (%d).",
329-
outputSize, outSize + bufferOutSize);
330-
av_frame_free(&frame);
331-
return AUDIO_DECODER_ERROR_INVALID_DATA;
370+
LOGD(
371+
"Output buffer size (%d) too small for output data (%d), "
372+
"reallocating buffer.",
373+
outputSize, outSize + bufferOutSize);
374+
outputSize = outSize + bufferOutSize;
375+
outputBuffer = growBuffer(outputSize);
376+
if (!outputBuffer) {
377+
LOGE("Failed to reallocate output buffer.");
378+
av_frame_free(&frame);
379+
return AUDIO_DECODER_ERROR_OTHER;
380+
}
332381
}
333382
result = swr_convert(resampleContext, &outputBuffer, bufferOutSize,
334383
(const uint8_t **)frame->data, frame->nb_samples);

0 commit comments

Comments
 (0)