-
Notifications
You must be signed in to change notification settings - Fork 468
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ffmpeg: reallocate output buffer dynamically #746
Changes from all commits
adee462
8750ed8
36fb207
78142d3
c4c4ef9
58d8850
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,8 @@ extern "C" { | |
#define LOG_TAG "ffmpeg_jni" | ||
#define LOGE(...) \ | ||
((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) | ||
#define LOGD(...) \ | ||
((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) | ||
|
||
#define LIBRARY_FUNC(RETURN_TYPE, NAME, ...) \ | ||
extern "C" { \ | ||
|
@@ -67,6 +69,8 @@ static const AVSampleFormat OUTPUT_FORMAT_PCM_FLOAT = AV_SAMPLE_FMT_FLT; | |
static const int AUDIO_DECODER_ERROR_INVALID_DATA = -1; | ||
static const int AUDIO_DECODER_ERROR_OTHER = -2; | ||
|
||
static jmethodID growOutputBufferMethod; | ||
|
||
/** | ||
* Returns the AVCodec with the specified name, or NULL if it is not available. | ||
*/ | ||
|
@@ -81,13 +85,21 @@ AVCodecContext *createContext(JNIEnv *env, AVCodec *codec, jbyteArray extraData, | |
jboolean outputFloat, jint rawSampleRate, | ||
jint rawChannelCount); | ||
|
||
struct GrowOutputBufferCallback { | ||
uint8_t *operator()(int requiredSize) const; | ||
|
||
JNIEnv *env; | ||
jobject thiz; | ||
jobject decoderOutputBuffer; | ||
}; | ||
|
||
/** | ||
* Decodes the packet into the output buffer, returning the number of bytes | ||
* written, or a negative AUDIO_DECODER_ERROR constant value in the case of an | ||
* error. | ||
*/ | ||
int decodePacket(AVCodecContext *context, AVPacket *packet, | ||
uint8_t *outputBuffer, int outputSize); | ||
uint8_t *outputBuffer, int outputSize, GrowOutputBufferCallback growBuffer); | ||
|
||
/** | ||
* Transforms ffmpeg AVERROR into a negative AUDIO_DECODER_ERROR constant value. | ||
|
@@ -107,6 +119,17 @@ void releaseContext(AVCodecContext *context); | |
jint JNI_OnLoad(JavaVM *vm, void *reserved) { | ||
JNIEnv *env; | ||
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) { | ||
LOGE("JNI_OnLoad: GetEnv failed"); | ||
return -1; | ||
} | ||
jclass clazz = env->FindClass("androidx/media3/decoder/ffmpeg/FfmpegAudioDecoder"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you reference classes and methods by name from native code, you'll also need to add them to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
if (!clazz) { | ||
LOGE("JNI_OnLoad: FindClass failed"); | ||
return -1; | ||
} | ||
growOutputBufferMethod = env->GetMethodID(clazz, "growOutputBuffer","(Landroidx/media3/decoder/SimpleDecoderOutputBuffer;I)Ljava/nio/ByteBuffer;"); | ||
if (!growOutputBufferMethod) { | ||
LOGE("JNI_OnLoad: GetMethodID failed"); | ||
return -1; | ||
} | ||
avcodec_register_all(); | ||
|
@@ -138,12 +161,12 @@ AUDIO_DECODER_FUNC(jlong, ffmpegInitialize, jstring codecName, | |
} | ||
|
||
AUDIO_DECODER_FUNC(jint, ffmpegDecode, jlong context, jobject inputData, | ||
jint inputSize, jobject outputData, jint outputSize) { | ||
jint inputSize, jobject decoderOutputBuffer, jobject outputData, jint outputSize) { | ||
if (!context) { | ||
LOGE("Context must be non-NULL."); | ||
return -1; | ||
} | ||
if (!inputData || !outputData) { | ||
if (!inputData || !decoderOutputBuffer || !outputData) { | ||
LOGE("Input and output buffers must be non-NULL."); | ||
return -1; | ||
} | ||
|
@@ -162,7 +185,17 @@ AUDIO_DECODER_FUNC(jint, ffmpegDecode, jlong context, jobject inputData, | |
packet.data = inputBuffer; | ||
packet.size = inputSize; | ||
return decodePacket((AVCodecContext *)context, &packet, outputBuffer, | ||
outputSize); | ||
outputSize, GrowOutputBufferCallback{env, thiz, decoderOutputBuffer}); | ||
} | ||
|
||
uint8_t *GrowOutputBufferCallback::operator()(int requiredSize) const { | ||
jobject newOutputData = env->CallObjectMethod(thiz, growOutputBufferMethod, decoderOutputBuffer, requiredSize); | ||
if (env->ExceptionCheck()) { | ||
LOGE("growOutputBuffer() failed"); | ||
env->ExceptionDescribe(); | ||
return nullptr; | ||
} | ||
return static_cast<uint8_t *>(env->GetDirectBufferAddress(newOutputData)); | ||
} | ||
|
||
AUDIO_DECODER_FUNC(jint, ffmpegGetChannelCount, jlong context) { | ||
|
@@ -264,7 +297,7 @@ AVCodecContext *createContext(JNIEnv *env, AVCodec *codec, jbyteArray extraData, | |
} | ||
|
||
int decodePacket(AVCodecContext *context, AVPacket *packet, | ||
uint8_t *outputBuffer, int outputSize) { | ||
uint8_t *outputBuffer, int outputSize, GrowOutputBufferCallback growBuffer) { | ||
int result = 0; | ||
// Queue input data. | ||
result = avcodec_send_packet(context, packet); | ||
|
@@ -320,15 +353,20 @@ int decodePacket(AVCodecContext *context, AVPacket *packet, | |
} | ||
context->opaque = resampleContext; | ||
} | ||
int inSampleSize = av_get_bytes_per_sample(sampleFormat); | ||
|
||
int outSampleSize = av_get_bytes_per_sample(context->request_sample_fmt); | ||
int outSamples = swr_get_out_samples(resampleContext, sampleCount); | ||
int bufferOutSize = outSampleSize * channelCount * outSamples; | ||
if (outSize + bufferOutSize > outputSize) { | ||
LOGE("Output buffer size (%d) too small for output data (%d).", | ||
LOGD("Output buffer size (%d) too small for output data (%d), reallocating buffer.", | ||
outputSize, outSize + bufferOutSize); | ||
av_frame_free(&frame); | ||
return AUDIO_DECODER_ERROR_INVALID_DATA; | ||
outputSize = outSize + bufferOutSize; | ||
outputBuffer = growBuffer(outputSize); | ||
if (!outputBuffer) { | ||
LOGE("Failed to reallocate output buffer."); | ||
av_frame_free(&frame); | ||
return AUDIO_DECODER_ERROR_OTHER; | ||
} | ||
} | ||
result = swr_convert(resampleContext, &outputBuffer, bufferOutSize, | ||
(const uint8_t **)frame->data, frame->nb_samples); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add Javadoc to this method in the style of other existing Javadoc?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.