Decode AAC to PCM with ffmpeg on android

2019-05-21 13:15发布

问题:

I have built ffmpeg 0.8.12 (love) with the android NDK (r8c) on ubuntu. I then use the generated library in another android application through JNI.

Essentially what I want to do is pass a byte stream from java to my c jni function and use ffmpeg to decode it into a PCM audio buffer which will then be passed back to java to be played using Android's AudioTrack. I can successfully pass the buffer through to jni (have checked the values) and ffmpeg seems to initialise correctly, but when it tries to decode the first frame, it throws an error in the aac_decode_frame_int method in aacdec.c "channel element 0.0 is not allocated". The aac file plays fine and is valid.

Here is my jni code to do the decoding

jint Java_com_example_testffmpeg_MainActivity_decodeAacBytes(JNIEnv * env,
        jobject this, jbyteArray input, jint numBytes) {

    //copy bytes from java
    jbyte* bufferPtr = (*env)->GetByteArrayElements(env, input, NULL);
    uint8_t inputBytes[numBytes + FF_INPUT_BUFFER_PADDING_SIZE];
    memset(inputBytes, 0, numBytes + FF_INPUT_BUFFER_PADDING_SIZE);
    memcpy(inputBytes, bufferPtr, numBytes);
    (*env)->ReleaseByteArrayElements(env, input, bufferPtr, 0);

    av_register_all();

    AVCodec *codec = avcodec_find_decoder(CODEC_ID_AAC);

    if (codec == NULL) {
        LOGE("Cant find AAC codec\n");
        return 0;
    }
    LOGI("AAC codec found\n");

    AVCodecContext *avCtx = avcodec_alloc_context();

    if (avCtx == NULL) {
        LOGE("Could not allocate codec context\n");
        return 0;
    }
    LOGI("codec context allocated\n");

    if (avcodec_open2(avCtx, codec, NULL) < 0) {
        LOGE("Could not open codec\n");
        return 0;
    }
    LOGI("AAC codec opened");

    //the input buffer
    AVPacket avPacket;
    av_init_packet(&avPacket);

    LOGI("AVPacket initialised\n");

    avPacket.size = numBytes; //input buffer size
    avPacket.data = inputBytes; // the input buffer

    int outSize;
    int len;
    uint8_t *outbuf = malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE);

    while (avPacket.size > 0) {
        outSize = AVCODEC_MAX_AUDIO_FRAME_SIZE;
        len = avcodec_decode_audio3(avCtx, (short *) outbuf, &outSize,
                &avPacket);

        if (len < 0) {
            LOGE("Error while decoding\n");
            return 0;
        }

        if (outSize > 0) {
            LOGI("Decoded some stuff\n");
        }

        avPacket.size -= len;
        avPacket.data += len;
    }

    LOGI("Freeing memory\n");

    av_free_packet(&avPacket);
    avcodec_close(avCtx);
    av_free(avCtx);

    return 0;
}

The problem occurs in the call to avcodec_decode_audio3, when the decoding first occurs. I have stepped through the ffmpeg code, but can't find the problem. Any help would be greatly appreciated!

回答1:

You must set some additional settings for AVCodecContext before you call avcodec_open2.

I usually set these required settings (variables beginning with 'k' mean predefined constatns):

avCtx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
avCtx->codec_type = AVMEDIA_TYPE_AUDIO;
avCtx->channels = kChannelsCount; // for example, 2
avCtx->sample_fmt = kSampleFmt; // AV_SAMPLE_FMT_S16
avCtx->sample_rate = kSampleRate; // 44100
avCtx->channel_layout = kSampleLayout; // 3
const AVRational timeBase = {1, avCtx->sample_rate};
avCtx->time_base = timeBase;

UPD

I'm sorry, I wrote the parameters that must be set for audio encoding. For audio decoding usually sufficient to set avCtx->channels, ctx->sample_rate or set avCtx->extrdata and avCtx->extradata_size.

To find the cause of the error, try to see the ffmpeg output. If on the device is difficult to do, you can redirect the ffmpeg output and perform logging by own callback. Example:

    // initialize:
    ::av_log_set_callback(&my_ffmpeg_log);

    // callback
    void my_ffmpeg_log(void *ptr, int level, const char *fmt, va_list vl)
    {
      /// Here you can set a more detailed level
      if (level < AV_LOG_VERBOSE)
      {
        static char message[8192];
        const char *module = NULL;

        if (ptr)
        {
          AVClass *avc = *(AVClass**) ptr;
          if (avc->item_name)
            module = avc->item_name(ptr);
        }
        vsnprintf(message, sizeof message, fmt, vl);
        // you can set own function here, for example LOGI, as you have in your example
        std::cout << "ffmpeg message : " << module << " " << level << " " << message;
      }
    }