一步步实现windows版ijkplayer系列文章之一——Windows10平台编译ffmpeg 4.0.2,生成ffplay
一步步实现windows版ijkplayer系列文章之二——Ijkplayer播放器源码分析之音视频输出——视频篇
一步步实现windows版ijkplayer系列文章之三——Ijkplayer播放器源码分析之音视频输出——音频篇
一步步实现windows版ijkplayer系列文章之四——windows下编译ijkplyer版ffmpeg
一步步实现windows版ijkplayer系列文章之五——使用automake一步步生成makefile
一步步实现windows版ijkplayer系列文章之六——SDL2源码分析之OpenGL ES在windows上的渲染过程
一步步实现windows版ijkplayer系列文章之七——终结篇(附源码)html
这篇文章的ijkplayer音频源码研究咱们仍是选择Android平台,它的音频解码是不支持硬解的,音频播放使用的API是OpenSL ES或AudioTrack。java
什么是OpenSL ES?下面来自官网的说明:android
OpenSL ES™ is a royalty-free, cross-platform, hardware-accelerated audio API tuned for embedded systems. It provides a standardized, high-performance, low-latency method to access audio functionality for developers of native applications on embedded mobile multimedia devices, enabling straightforward cross-platform deployment of hardware and software audio capabilities, reducing implementation effort, and promoting the market for advanced audio.git
可见OpenGL ES是专门为嵌入式设备设计的音频API,因此不适合在PC上使用。github
AudioTrack是专门为Android应用提供的java API,显然也不适合在PC上使用。windows
使用AudioTrack API来输出音频就须要把音频数据从java层拷贝到native层。而OpenSL ES API是Android NDK提供的native接口,它能够在native层直接获取和处理数据,所以为了提升效率,应该使用OpenSL ES API。经过以下java接口设置音频输出API:app
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 0);
Ijkplayer使用jni4android来为AudioTrack的java API自动生成JNI native代码。ide
咱们尽可能选择底层的代码来进行研究,所以本篇文章梳理一遍OpenSL ES API在ijkplayer中的使用。函数
调用以下函数生成音频输出对象:源码分析
SDL_Aout *SDL_AoutAndroid_CreateForOpenSLES()
建立并初始化Audio Engine:
//建立 SLObjectItf slObject = NULL; ret = slCreateEngine(&slObject, 0, NULL, 0, NULL, NULL); CHECK_OPENSL_ERROR(ret, "%s: slCreateEngine() failed", __func__); opaque->slObject = slObject; //初始化 ret = (*slObject)->Realize(slObject, SL_BOOLEAN_FALSE); CHECK_OPENSL_ERROR(ret, "%s: slObject->Realize() failed", __func__); //获取SLEngine接口对象slEngine SLEngineItf slEngine = NULL; ret = (*slObject)->GetInterface(slObject, SL_IID_ENGINE, &slEngine); CHECK_OPENSL_ERROR(ret, "%s: slObject->GetInterface() failed", __func__); opaque->slEngine = slEngine;
打开音频输出设备:
//使用slEngine打开输出设备 SLObjectItf slOutputMixObject = NULL; const SLInterfaceID ids1[] = {SL_IID_VOLUME}; const SLboolean req1[] = {SL_BOOLEAN_FALSE}; ret = (*slEngine)->CreateOutputMix(slEngine, &slOutputMixObject, 1, ids1, req1); CHECK_OPENSL_ERROR(ret, "%s: slEngine->CreateOutputMix() failed", __func__); opaque->slOutputMixObject = slOutputMixObject; //初始化 ret = (*slOutputMixObject)->Realize(slOutputMixObject, SL_BOOLEAN_FALSE); CHECK_OPENSL_ERROR(ret, "%s: slOutputMixObject->Realize() failed", __func__);
将上述建立的OpenSL ES相关对象保存到SDL_Aout_Opaque中。
设置播放器音频输出对象的回调函数:
aout->free_l = aout_free_l; aout->opaque_class = &g_opensles_class; aout->open_audio = aout_open_audio; aout->pause_audio = aout_pause_audio; aout->flush_audio = aout_flush_audio; aout->close_audio = aout_close_audio; aout->set_volume = aout_set_volume; aout->func_get_latency_seconds = aout_get_latency_seconds;
经过以下函数进行:
static int aout_open_audio(SDL_Aout *aout, const SDL_AudioSpec *desired, SDL_AudioSpec *obtained)
配置数据源
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, OPENSLES_BUFFERS }; SLDataFormat_PCM *format_pcm = &opaque->format_pcm; format_pcm->formatType = SL_DATAFORMAT_PCM; format_pcm->numChannels = desired->channels; format_pcm->samplesPerSec = desired->freq * 1000; // milli Hz format_pcm->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; format_pcm->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; switch (desired->channels) { case 2: format_pcm->channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; break; case 1: format_pcm->channelMask = SL_SPEAKER_FRONT_CENTER; break; default: ALOGE("%s, invalid channel %d", __func__, desired->channels); goto fail; } format_pcm->endianness = SL_BYTEORDER_LITTLEENDIAN; SLDataSource audio_source = {&loc_bufq, format_pcm};
配置数据管道
SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, opaque->slOutputMixObject }; SLDataSink audio_sink = {&loc_outmix, NULL};
其它参数
const SLInterfaceID ids2[] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME, SL_IID_PLAY }; static const SLboolean req2[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
建立播放器
ret = (*slEngine)->CreateAudioPlayer(slEngine, &slPlayerObject, &audio_source, &audio_sink, sizeof(ids2) / sizeof(*ids2), ids2, req2);
获取相关接口
//获取seek和play接口 ret = (*slPlayerObject)->GetInterface(slPlayerObject, SL_IID_PLAY, &opaque->slPlayItf); CHECK_OPENSL_ERROR(ret, "%s: slPlayerObject->GetInterface(SL_IID_PLAY) failed", __func__); //音量调节接口 ret = (*slPlayerObject)->GetInterface(slPlayerObject, SL_IID_VOLUME, &opaque->slVolumeItf); CHECK_OPENSL_ERROR(ret, "%s: slPlayerObject->GetInterface(SL_IID_VOLUME) failed", __func__); //获取音频输出的BufferQueue接口 ret = (*slPlayerObject)->GetInterface(slPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &opaque->slBufferQueueItf); CHECK_OPENSL_ERROR(ret, "%s: slPlayerObject->GetInterface(SL_IID_ANDROIDSIMPLEBUFFERQUEUE) failed", __func__);
设置回调函数
回调函数并不传递音频数据,它只是告诉程序:我已经准备好接受处理(播放)数据了。这时候就能够调用Enqueue向BufferQueue中插入音频数据了。
ret = (*opaque->slBufferQueueItf)->RegisterCallback(opaque->slBufferQueueItf, aout_opensles_callback, (void*)aout); CHECK_OPENSL_ERROR(ret, "%s: slBufferQueueItf->RegisterCallback() failed", __func__);
初始化其它参数
opaque->bytes_per_frame = format_pcm->numChannels * format_pcm->bitsPerSample / 8;//每一帧的bytes数,此处将一个采样点做为一帧 opaque->milli_per_buffer = OPENSLES_BUFLEN;//一个buffer中的音频时长,单位为milliseconds opaque->frames_per_buffer = opaque->milli_per_buffer * format_pcm->samplesPerSec / 1000000; // samplesPerSec is in milli,一个buffer中的音频时长*每秒的样本(帧)数,获得每一个音频buffer中的帧数 opaque->bytes_per_buffer = opaque->bytes_per_frame * opaque->frames_per_buffer;//最后求出每一个buffer中含有的byte数目。 opaque->buffer_capacity = OPENSLES_BUFFERS * opaque->bytes_per_buffer;
此回调函数每执行一次Dequeue会被执行一次。
音频数据的处理为典型的生产者消费者模型,解码线程解码出音频数据插入到队列中,音频驱动程序取出数据将声音播放出来。
audio_thread函数为音频解码线程主函数:
static int audio_thread(void *arg){ do { ffp_audio_statistic_l(ffp); if ((got_frame = decoder_decode_frame(ffp, &is->auddec, frame, NULL)) < 0)//从PacketQueue中取出pakcet并进行解码,生成一帧数据 ... if (!(af = frame_queue_peek_writable(&is->sampq))) goto the_end; af->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb); af->pos = frame->pkt_pos; af->serial = is->auddec.pkt_serial; af->duration = av_q2d((AVRational){frame->nb_samples, frame->sample_rate}); av_frame_move_ref(af->frame, frame); frame_queue_push(&is->sampq);//将帧数据插入帧队列 FrameQueue }
aout_thread_n 为音频输出线程主函数:
static int aout_thread_n(SDL_Aout *aout){ ... SDL_LockMutex(opaque->wakeup_mutex); //若是没有退出播放&&(当前播放器状态为暂停||插入音频BufferQueue中的数据条数大于OPENSLES_BUFFERS) if (!opaque->abort_request && (opaque->pause_on || slState.count >= OPENSLES_BUFFERS)) { //不知道为何if下面又加了一层while?? while (!opaque->abort_request && (opaque->pause_on || slState.count >= OPENSLES_BUFFERS)) { //若是此时为非暂停状态,将播放器状态置为PLAYING if (!opaque->pause_on) { (*slPlayItf)->SetPlayState(slPlayItf, SL_PLAYSTATE_PLAYING); } //若是暂停或者队列中数据过多,这里都会等待一个条件变量,并将过时时间置为1秒,应该是防止BufferQueue中的数据再也不快速增长 SDL_CondWaitTimeout(opaque->wakeup_cond, opaque->wakeup_mutex, 1000); SLresult slRet = (*slBufferQueueItf)->GetState(slBufferQueueItf, &slState); if (slRet != SL_RESULT_SUCCESS) { ALOGE("%s: slBufferQueueItf->GetState() failed\n", __func__); SDL_UnlockMutex(opaque->wakeup_mutex); } //暂停播放 if (opaque->pause_on) (*slPlayItf)->SetPlayState(slPlayItf, SL_PLAYSTATE_PAUSED); } //恢复播放 if (!opaque->abort_request && !opaque->pause_on) { (*slPlayItf)->SetPlayState(slPlayItf, SL_PLAYSTATE_PLAYING); } } ... next_buffer = opaque->buffer + next_buffer_index * bytes_per_buffer; next_buffer_index = (next_buffer_index + 1) % OPENSLES_BUFFERS; //调用回调函数生成插入到BufferQueue中的数据 audio_cblk(userdata, next_buffer, bytes_per_buffer); //若是须要刷新BufferQueue数据,则清除数据,什么时候须要清理数据??解释在下面 if (opaque->need_flush) { (*slBufferQueueItf)->Clear(slBufferQueueItf); opaque->need_flush = false; } //不知道为何会判断两次?? if (opaque->need_flush) { ALOGE("flush"); opaque->need_flush = 0; (*slBufferQueueItf)->Clear(slBufferQueueItf); } else { //最终将数据插入到BufferQueue中。 slRet = (*slBufferQueueItf)->Enqueue(slBufferQueueItf, next_buffer, bytes_per_buffer); ... }
如下是为条件变量opaque->wakeup_cond 发送signal的几个函数,目的是让输出线程快速响应
static void aout_set_volume(SDL_Aout *aout, float left_volume, float right_volume)
第五个为设置音量函数,立刻设置音量。
经过调用以下函数生成插入到BufferQueue中的数据 :
static void sdl_audio_callback(void *opaque, Uint8 *stream, int len){ ... if (is->audio_buf_index >= is->audio_buf_size) { //若是buffer中没有数据了,生成新数据。 audio_size = audio_decode_frame(ffp); ... if (!is->muted && is->audio_buf && is->audio_volume == SDL_MIX_MAXVOLUME) //直接拷贝到stream 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); } }
生成新数据的函数不是对音频数据进行解码,而是对帧数据进行了二次处理,对音频进行了必要的重采样或者变速变调。
static int audio_decode_frame(FFPlayer *ffp){ ... //重采样 len2 = swr_convert(is->swr_ctx, out, out_count, in, af->frame->nb_samples); ... //音频变速变调 int ret_len = ijk_soundtouch_translate(is->handle, is->audio_new_buf, (float)(ffp->pf_playback_rate), (float)(1.0f/ffp->pf_playback_rate), resampled_data_size / 2, bytes_per_sample, is->audio_tgt.channels, af->frame->sample_rate); ... //最后将数据保存到audio_buf中 is->audio_buf = (uint8_t*)is->audio_new_buf; ... }
最后一个比较让人困惑的问题是什么时候才会清理BufferQueue,看一下清理的命令是在什么时候发出的:
static void sdl_audio_callback(void *opaque, Uint8 *stream, int len) { ... if (is->auddec.pkt_serial != is->audioq.serial) { is->audio_buf_index = is->audio_buf_size; memset(stream, 0, len); // stream += len; // len = 0; SDL_AoutFlushAudio(ffp->aout); break; } ... }
它是在音频输出线程中获取即将插入到BufferQueue的音频数据,调用回调函数时发出的,发出的条件如上所示,其中pkt_serial 为从PacketQueue队列中取出的须要解码的packet的serial,serial为当前PacketQueue队列的serial。也就是说,若是二者不等,就须要清理BufferQueue。这里的serial是要保证先后数据包的连续性,例如发生了Seek,数据不连续,就须要清理旧数据。
注:在播放器中的VideoState成员中,audioq和解码成员auddec中的queue是同一个队列。
decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread);
笔者从头至尾把和音频输出相关的自认为重要的源码作了一些解释和记录,有些细节没有去深刻研究。之后有时间慢慢学习。