音频播放有一部分在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代码输出