ijkplayer 音频播放线程

音频播放有一部分在ijkplayer读线程中提到过,就是在读线程read_thread()中调用的stream_component_open()函数中会调用audio_open(),然后会调用aout->open_audio()函数,这个函数指针的赋值在ijkplayer初始化流程中的ff_ffpipeline_android.c/ func_open_audio_output()函数赋值,func_open_audio_output函数会先选择使用audiotrack还是opensles,这里以audiotrack为例,最终会调用SDL_CreateThreadEx(..., aout_thread, aout, ...)创建音频输出线程

音频输出线程aout_thread函数里面又调用ijksdl_aout_android_audiotrack.c/aout_thread_n()函数,下面来看一下该函数的重点代码

static int aout_thread_n(JNIEnv *env, SDL_Aout *aout)
{
        ...
        audio_cblk(userdata, buffer, copy_size);
        ...
        if (opaque->need_flush) {
            opaque->need_flush = 0;
            SDL_Android_AudioTrack_flush(env, atrack);
        } else {
            int written = SDL_Android_AudioTrack_write(env, atrack, buffer, copy_size);
            if (written != copy_size) {
                ALOGW("AudioTrack: not all data copied %d/%d", (int)written, (int)copy_size);
            }
        // TODO: 1 if callback return -1 or 0
    }
    ...
}

可以看出音频播放线程这里主要干了两件事

  1. 调用回调函数audio_cblk
  2. 调用SDL_Android_AudioTrack_write()

1. 调用回调函数audio_cblk
首先看一下第一步,调用audio_cblk,该回调函数指针的赋值是在ff_ffplayer.c/audio_open(),里面有句话

wanted_spec.callback = sdl_audio_callback

调用audio_cblk其实就是调用来sdl_audio_callback,具体赋值有点点曲折,可以自己跟一下,来看看sdl_audio_callback函数做了些什么

/* prepare a new audio buffer */
static void sdl_audio_callback(void *opaque, Uint8 *stream, int len)
{
    ...
    while (len > 0) {
        if (is->audio_buf_index >= is->audio_buf_size) {
           audio_size = audio_decode_frame(ffp);
           ...
        }
        ...
        if (!is->muted && is->audio_buf && is->audio_volume == SDL_MIX_MAXVOLUME)
            memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
        else {
            memset(stream, 0, len1);
            if (!is->muted && is->audio_buf)
                SDL_MixAudio(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1, is->audio_volume);
        }
        len -= len1;
        stream += len1;
        is->audio_buf_index += len1;
    }
    ...
}

sdl_audio_callback函数调用了ff_ffplayer.c/audio_decode_frame(),先来看看audio_decode_frame代码

/**
 * Decode one audio frame and return its uncompressed size.
 *
 * The processed audio frame is decoded, converted if required, and
 * stored in is->audio_buf, with size in bytes given by the return
 * value.
 */
static int audio_decode_frame(FFPlayer *ffp)
{
    ...
reload:
    do {
        ...
        if (!(af = frame_queue_peek_readable(&is->sampq)))
            return -1;
        frame_queue_next(&is->sampq);
    } while (af->serial != is->audioq.serial);
    
    ...
    if (is->swr_ctx) {
            ...
    } else {
        is->audio_buf = af->frame->data[0];
        resampled_data_size = data_size;
    }
    ...
}

上面函数中的ff_ffplayer.c/frame_queue_peek_readable()和ff_ffplayer.c/frame_queue_next()函数类似与ijkplayer 音频解码线程中将的frame_queue_peek_writable()和frame_queue_push(),ff_ffplayer.c/frame_queue_peek_readable()检查sampq是否为空,如果为空则阻塞,接着ff_ffplayer.c/frame_queue_next()用来唤醒sampq的写操作,因为当sampq不可写时也会阻塞,最后is->audio_buf = af->frame->data[0]给audio_buf赋值

sdl_audio_callback函数调用完ff_ffplayer.c/audio_decode_frame()后,将赋好值的audio_buf给了stream,这个stream时调用audio_cblk(userdata, buffer, copy_size)传进来的这个buffer,说白了sdl_audio_callback函数也就是将sampq中的一帧给了这个buffer

2. 调用SDL_Android_AudioTrack_write()
现在看音频播放函数中的第二步调用SDL_Android_AudioTrack_write(env, atrack, buffer, copy_size),这个buffer就是刚刚从回调函数sdl_audio_callback取出来的也就是从sampq中取出来的,怎么用这个buffer,看下面代码

int SDL_Android_AudioTrack_write(JNIEnv *env, SDL_Android_AudioTrack *atrack, uint8_t *data, int size_in_byte)
{
    ...
    (*env)->SetByteArrayRegion(env, atrack->byte_buffer, 0, (int)size_in_byte, (jbyte*) data);
    if (J4A_ExceptionCheck__catchAll(env))
        return -1;

    int retval = J4AC_AudioTrack__write(env, atrack->thiz, atrack->byte_buffer, 0, (int)size_in_byte);
    ...
}

这里将buffer也就是函数里的data放入byte_buffer中,后面调用 J4AC_AudioTrack__write函数最后调用java代码输出。

这里音频播放线程流程就结束了,其实就是干了两件事,取sampq中解码好的音频帧,然后传给java代码输出

你可能感兴趣的:(ijkplayer 音频播放线程)