美文网首页ijkplayer
ijkplayer 音频播放线程

ijkplayer 音频播放线程

作者: ce0b74704937 | 来源:发表于2018-06-05 21:54 被阅读0次

    音频播放有一部分在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 音频播放线程

        本文链接:https://www.haomeiwen.com/subject/jrwmsftx.html