| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include <jni.h> |
| #include <stdlib.h> |
| #include <android/log.h> |
| |
| extern "C" { |
| #ifdef __cplusplus |
| #define __STDC_CONSTANT_MACROS |
| #ifdef _STDINT_H |
| #undef _STDINT_H |
| #endif |
| #include <stdint.h> |
| #endif |
| #include <libavcodec/avcodec.h> |
| #include <libavutil/channel_layout.h> |
| #include <libavutil/error.h> |
| #include <libavutil/opt.h> |
| #include <libswresample/swresample.h> |
| } |
| |
| #define LOG_TAG "ffmpeg_jni" |
| #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, \ |
| __VA_ARGS__)) |
| |
| #define LIBRARY_FUNC(RETURN_TYPE, NAME, ...) \ |
| extern "C" { \ |
| JNIEXPORT RETURN_TYPE \ |
| Java_com_google_android_exoplayer2_ext_ffmpeg_FfmpegLibrary_##NAME( \ |
| JNIEnv *env, jobject thiz, ##__VA_ARGS__); \ |
| } \ |
| JNIEXPORT RETURN_TYPE \ |
| Java_com_google_android_exoplayer2_ext_ffmpeg_FfmpegLibrary_##NAME( \ |
| JNIEnv *env, jobject thiz, ##__VA_ARGS__) |
| |
| #define AUDIO_DECODER_FUNC(RETURN_TYPE, NAME, ...) \ |
| extern "C" { \ |
| JNIEXPORT RETURN_TYPE \ |
| Java_com_google_android_exoplayer2_ext_ffmpeg_FfmpegAudioDecoder_##NAME( \ |
| JNIEnv *env, jobject thiz, ##__VA_ARGS__); \ |
| } \ |
| JNIEXPORT RETURN_TYPE \ |
| Java_com_google_android_exoplayer2_ext_ffmpeg_FfmpegAudioDecoder_##NAME( \ |
| JNIEnv *env, jobject thiz, ##__VA_ARGS__) |
| |
| #define ERROR_STRING_BUFFER_LENGTH 256 |
| |
| // Output format corresponding to AudioFormat.ENCODING_PCM_16BIT. |
| static const AVSampleFormat OUTPUT_FORMAT_PCM_16BIT = AV_SAMPLE_FMT_S16; |
| // Output format corresponding to AudioFormat.ENCODING_PCM_FLOAT. |
| static const AVSampleFormat OUTPUT_FORMAT_PCM_FLOAT = AV_SAMPLE_FMT_FLT; |
| |
| // LINT.IfChange |
| static const int AUDIO_DECODER_ERROR_INVALID_DATA = -1; |
| static const int AUDIO_DECODER_ERROR_OTHER = -2; |
| // LINT.ThenChange(../java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegAudioDecoder.java) |
| |
| /** |
| * Returns the AVCodec with the specified name, or NULL if it is not available. |
| */ |
| AVCodec *getCodecByName(JNIEnv* env, jstring codecName); |
| |
| /** |
| * Allocates and opens a new AVCodecContext for the specified codec, passing the |
| * provided extraData as initialization data for the decoder if it is non-NULL. |
| * Returns the created context. |
| */ |
| AVCodecContext *createContext(JNIEnv *env, AVCodec *codec, jbyteArray extraData, |
| jboolean outputFloat, jint rawSampleRate, |
| jint rawChannelCount); |
| |
| /** |
| * 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); |
| |
| /** |
| * Outputs a log message describing the avcodec error number. |
| */ |
| void logError(const char *functionName, int errorNumber); |
| |
| /** |
| * Releases the specified context. |
| */ |
| 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) { |
| return -1; |
| } |
| avcodec_register_all(); |
| return JNI_VERSION_1_6; |
| } |
| |
| LIBRARY_FUNC(jstring, ffmpegGetVersion) { |
| return env->NewStringUTF(LIBAVCODEC_IDENT); |
| } |
| |
| LIBRARY_FUNC(jboolean, ffmpegHasDecoder, jstring codecName) { |
| return getCodecByName(env, codecName) != NULL; |
| } |
| |
| AUDIO_DECODER_FUNC(jlong, ffmpegInitialize, jstring codecName, |
| jbyteArray extraData, jboolean outputFloat, |
| jint rawSampleRate, jint rawChannelCount) { |
| AVCodec *codec = getCodecByName(env, codecName); |
| if (!codec) { |
| LOGE("Codec not found."); |
| return 0L; |
| } |
| return (jlong)createContext(env, codec, extraData, outputFloat, rawSampleRate, |
| rawChannelCount); |
| } |
| |
| AUDIO_DECODER_FUNC(jint, ffmpegDecode, jlong context, jobject inputData, |
| jint inputSize, jobject outputData, jint outputSize) { |
| if (!context) { |
| LOGE("Context must be non-NULL."); |
| return -1; |
| } |
| if (!inputData || !outputData) { |
| LOGE("Input and output buffers must be non-NULL."); |
| return -1; |
| } |
| if (inputSize < 0) { |
| LOGE("Invalid input buffer size: %d.", inputSize); |
| return -1; |
| } |
| if (outputSize < 0) { |
| LOGE("Invalid output buffer length: %d", outputSize); |
| return -1; |
| } |
| uint8_t *inputBuffer = (uint8_t *) env->GetDirectBufferAddress(inputData); |
| uint8_t *outputBuffer = (uint8_t *) env->GetDirectBufferAddress(outputData); |
| AVPacket packet; |
| av_init_packet(&packet); |
| packet.data = inputBuffer; |
| packet.size = inputSize; |
| return decodePacket((AVCodecContext *) context, &packet, outputBuffer, |
| outputSize); |
| } |
| |
| AUDIO_DECODER_FUNC(jint, ffmpegGetChannelCount, jlong context) { |
| if (!context) { |
| LOGE("Context must be non-NULL."); |
| return -1; |
| } |
| return ((AVCodecContext *) context)->channels; |
| } |
| |
| AUDIO_DECODER_FUNC(jint, ffmpegGetSampleRate, jlong context) { |
| if (!context) { |
| LOGE("Context must be non-NULL."); |
| return -1; |
| } |
| return ((AVCodecContext *) context)->sample_rate; |
| } |
| |
| AUDIO_DECODER_FUNC(jlong, ffmpegReset, jlong jContext, jbyteArray extraData) { |
| AVCodecContext *context = (AVCodecContext *) jContext; |
| if (!context) { |
| LOGE("Tried to reset without a context."); |
| return 0L; |
| } |
| |
| AVCodecID codecId = context->codec_id; |
| if (codecId == AV_CODEC_ID_TRUEHD) { |
| // Release and recreate the context if the codec is TrueHD. |
| // TODO: Figure out why flushing doesn't work for this codec. |
| releaseContext(context); |
| AVCodec *codec = avcodec_find_decoder(codecId); |
| if (!codec) { |
| LOGE("Unexpected error finding codec %d.", codecId); |
| return 0L; |
| } |
| jboolean outputFloat = |
| (jboolean)(context->request_sample_fmt == OUTPUT_FORMAT_PCM_FLOAT); |
| return (jlong)createContext(env, codec, extraData, outputFloat, |
| /* rawSampleRate= */ -1, |
| /* rawChannelCount= */ -1); |
| } |
| |
| avcodec_flush_buffers(context); |
| return (jlong) context; |
| } |
| |
| AUDIO_DECODER_FUNC(void, ffmpegRelease, jlong context) { |
| if (context) { |
| releaseContext((AVCodecContext *) context); |
| } |
| } |
| |
| AVCodec *getCodecByName(JNIEnv* env, jstring codecName) { |
| if (!codecName) { |
| return NULL; |
| } |
| const char *codecNameChars = env->GetStringUTFChars(codecName, NULL); |
| AVCodec *codec = avcodec_find_decoder_by_name(codecNameChars); |
| env->ReleaseStringUTFChars(codecName, codecNameChars); |
| return codec; |
| } |
| |
| AVCodecContext *createContext(JNIEnv *env, AVCodec *codec, jbyteArray extraData, |
| jboolean outputFloat, jint rawSampleRate, |
| jint rawChannelCount) { |
| AVCodecContext *context = avcodec_alloc_context3(codec); |
| if (!context) { |
| LOGE("Failed to allocate context."); |
| return NULL; |
| } |
| context->request_sample_fmt = |
| outputFloat ? OUTPUT_FORMAT_PCM_FLOAT : OUTPUT_FORMAT_PCM_16BIT; |
| if (extraData) { |
| jsize size = env->GetArrayLength(extraData); |
| context->extradata_size = size; |
| context->extradata = |
| (uint8_t *) av_malloc(size + AV_INPUT_BUFFER_PADDING_SIZE); |
| if (!context->extradata) { |
| LOGE("Failed to allocate extradata."); |
| releaseContext(context); |
| return NULL; |
| } |
| 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); |
| } |
| context->err_recognition = AV_EF_IGNORE_ERR; |
| int result = avcodec_open2(context, codec, NULL); |
| if (result < 0) { |
| logError("avcodec_open2", result); |
| releaseContext(context); |
| return NULL; |
| } |
| return context; |
| } |
| |
| int decodePacket(AVCodecContext *context, AVPacket *packet, |
| uint8_t *outputBuffer, int outputSize) { |
| int result = 0; |
| // Queue input data. |
| result = avcodec_send_packet(context, packet); |
| if (result) { |
| logError("avcodec_send_packet", result); |
| return result == AVERROR_INVALIDDATA ? AUDIO_DECODER_ERROR_INVALID_DATA |
| : AUDIO_DECODER_ERROR_OTHER; |
| } |
| |
| // Dequeue output data until it runs out. |
| int outSize = 0; |
| while (true) { |
| AVFrame *frame = av_frame_alloc(); |
| if (!frame) { |
| LOGE("Failed to allocate output frame."); |
| return -1; |
| } |
| result = avcodec_receive_frame(context, frame); |
| if (result) { |
| av_frame_free(&frame); |
| if (result == AVERROR(EAGAIN)) { |
| break; |
| } |
| logError("avcodec_receive_frame", result); |
| return result; |
| } |
| |
| // Resample output. |
| AVSampleFormat sampleFormat = context->sample_fmt; |
| int channelCount = context->channels; |
| int channelLayout = context->channel_layout; |
| int sampleRate = context->sample_rate; |
| int sampleCount = frame->nb_samples; |
| int dataSize = av_samples_get_buffer_size(NULL, channelCount, sampleCount, |
| sampleFormat, 1); |
| SwrContext *resampleContext; |
| if (context->opaque) { |
| resampleContext = (SwrContext *)context->opaque; |
| } else { |
| resampleContext = swr_alloc(); |
| av_opt_set_int(resampleContext, "in_channel_layout", channelLayout, 0); |
| av_opt_set_int(resampleContext, "out_channel_layout", channelLayout, 0); |
| av_opt_set_int(resampleContext, "in_sample_rate", sampleRate, 0); |
| av_opt_set_int(resampleContext, "out_sample_rate", sampleRate, 0); |
| av_opt_set_int(resampleContext, "in_sample_fmt", sampleFormat, 0); |
| // The output format is always the requested format. |
| av_opt_set_int(resampleContext, "out_sample_fmt", |
| context->request_sample_fmt, 0); |
| result = swr_init(resampleContext); |
| if (result < 0) { |
| logError("swr_init", result); |
| av_frame_free(&frame); |
| return -1; |
| } |
| 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).", |
| outputSize, outSize + bufferOutSize); |
| av_frame_free(&frame); |
| return -1; |
| } |
| result = swr_convert(resampleContext, &outputBuffer, bufferOutSize, |
| (const uint8_t **)frame->data, frame->nb_samples); |
| av_frame_free(&frame); |
| if (result < 0) { |
| logError("swr_convert", result); |
| return result; |
| } |
| int available = swr_get_out_samples(resampleContext, 0); |
| if (available != 0) { |
| LOGE("Expected no samples remaining after resampling, but found %d.", |
| available); |
| return -1; |
| } |
| outputBuffer += bufferOutSize; |
| outSize += bufferOutSize; |
| } |
| return outSize; |
| } |
| |
| void logError(const char *functionName, int errorNumber) { |
| char *buffer = (char *) malloc(ERROR_STRING_BUFFER_LENGTH * sizeof(char)); |
| av_strerror(errorNumber, buffer, ERROR_STRING_BUFFER_LENGTH); |
| LOGE("Error in %s: %s", functionName, buffer); |
| free(buffer); |
| } |
| |
| void releaseContext(AVCodecContext *context) { |
| if (!context) { |
| return; |
| } |
| SwrContext *swrContext; |
| if ((swrContext = (SwrContext *)context->opaque)) { |
| swr_free(&swrContext); |
| context->opaque = NULL; |
| } |
| avcodec_free_context(&context); |
| } |
| |