5 Audioplayer介绍

概述--------------------------------------------------------------------------------stagefrightplayer中使用audioplayer类来进行音频的输出。先来看下audioplayer相关的类图在之前介绍awesomeplayer结构的时候有画图介绍,mAudioPlayer的输入为mAudioSource,也就是解码器对应的OMXCodec,在构造AudioPlayer对象时会存放在其成员mSource中,而AudioPlayer可以看做是AudioOut的封装,实际的pcm playback操作都是借由AudioOut类来实现的。AudioOut继承自AudioSink,AudioSink为纯虚类,定义了接口,实际的工作由AudioTrack来完成(存放在mTrack中,以后介绍audioflinger时会详细介绍audiotrack)。本篇的主要内容如下:1 准备工作2 时间戳计算3 Seek功能下面就从代码详细介绍整个过程。1 准备工作--------------------------------------------------------------------------------在awesomeplayer中,与audioplayer相关的语句主要有:mAudioPlayer = new AudioPlayer(mAudioSink, allowDeepBuffering, this);mAudioPlayer->setSource(mAudioSource);startAudioPlayer_l();主要的调用位置在awesomeplayer中,代码如下 [html] view plaincopy1. status_t AwesomePlayer::play_l() { 2. ********************* 3. if (mAudioSource != NULL) { 4. if (mAudioPlayer == NULL) { 5. if (mAudioSink != NULL) { 6. mAudioPlayer = new AudioPlayer(mAudioSink); 7. mAudioPlayer->setSource(mAudioSource); 8. // We've already started the MediaSource in order to enable 9. // the prefetcher to read its data. 10. status_t err = mAudioPlayer->start( 11. true /* sourceAlreadyStarted */); 12. if (err != OK) { 13. delete mAudioPlayer; 14. mAudioPlayer = NULL; 15. mFlags &= ~(PLAYING | FIRST_FRAME); 16. return err; 17. } 18. delete mTimeSource; 19. mTimeSource = mAudioPlayer; 20. deferredAudioSeek = true; 21. mWatchForAudioSeekComplete = false; 22. mWatchForAudioEOS = true; 23. } 24. } else { 25. mAudioPlayer->resume(); 26. } 27. postCheckAudioStatusEvent_l(); 28. } 29. *************** 30. } 下面依次来看这几个调用1.1 构造函数这里首先要介绍一下mAudioSink ,当mAudioSink不为NULL的时候,AudioPlayer会将其传入构造函数。而且AudioPlayer中的播放操作都会依托mAudioSink来完成。从上面的类图可以知道,此处mAudioSink是从MediaPlayerService注册而来的AudioOut对象。具体代码在MediaPlayerservice中 [html] view plaincopy1. status_t MediaPlayerService::Client::setDataSource(*) 2. { 3. ************ 4. mAudioOutput = new AudioOutput(); 5. static_cast(p.get())->setAudioSink(mAudioOutput); 6. ************ 7. } 间接地调用到stagefrightplayer->setAudioSink,最终到awesomeplayer中,如下 [html] view plaincopy1. void AwesomePlayer::setAudioSink( 2. const sp &audioSink) { 3. Mutex::Autolock autoLock(mLock); 4. mAudioSink = audioSink; 5. } 而构造AudioPlayer时用到的就是mAudioSink成员,因此后面分析传入的mAudioSink的操作时,记住实际的对象为AudioOut对象,在MediaPlayerService定义。下面看实际的构造函数 [html] view plaincopy1. AudioPlayer::AudioPlayer(const sp &audioSink) 2. : mAudioTrack(NULL), 3. mInputBuffer(NULL), 4. mSampleRate(0), 5. mLatencyUs(0), 6. mFrameSize(0), 7. mNumFramesPlayed(0), 8. mPositionTimeMediaUs(-1), 9. mPositionTimeRealUs(-1), 10. mSeeking(false), 11. mReachedEOS(false), 12. mFinalStatus(OK), 13. mStarted(false), 14. mIsFirstBuffer(false), 15. mFirstBufferResult(OK), 16. mFirstBuffer(NULL), 17. mAudioSink(audioSink) { 18. } 主要是进行初始化,并将传入的mAudioSink存在成员mAudioSink中1.2 setSource在awesomeplayer中的调用语句为mAudioPlayer->setSource(mAudioSource);其中mAudioSource是OMXCodec对象,也就是解码器的输出mediabuffer,AudioPlayer中的实现如下 [html] view plaincopy1. void AudioPlayer::setSource(const sp &source) { 2. CHECK_EQ(mSource, NULL); 3. mSource = source; 4. } 只是简单的将其存在成员 mSource中1.3 startAudioPlayer_l()awesomeplayer中的实际代码主要是调用status_t err = mAudioPlayer->start(true/* sourceAlreadyStarted */);看下AudioPlayer实现 [html] view plaincopy1. status_t AudioPlayer::start(bool sourceAlreadyStarted) { 2. CHECK(!mStarted); 3. CHECK(mSource != NULL); 4. status_t err; 5. if (!sourceAlreadyStarted) { 6. err = mSource->start(); 7. if (err != OK) { 8. return err; 9. } 10. } 11. // We allow an optional INFO_FORMAT_CHANGED at the very beginning 12. // of playback, if there is one, getFormat below will retrieve the 13. // updated format, if there isn't, we'll stash away the valid buffer 14. // of data to be used on the first audio callback. 15. CHECK(mFirstBuffer == NULL); 16. mFirstBufferResult = mSource->read(&mFirstBuffer); 17. if (mFirstBufferResult == INFO_FORMAT_CHANGED) { 18. LOGV("INFO_FORMAT_CHANGED!!!"); 19. CHECK(mFirstBuffer == NULL); 20. mFirstBufferResult = OK; 21. mIsFirstBuffer = false; 22. } else { 23. mIsFirstBuffer = true; 24. } 25. sp format = mSource->getFormat(); 26. const char *mime; 27. bool success = format->findCString(kKeyMIMEType, &mime); 28. CHECK(success); 29. CHECK(!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_RAW)); 30. success = format->findInt32(kKeySampleRate, &mSampleRate); 31. CHECK(success); 32. int32_t numChannels; 33. success = format->findInt32(kKeyChannelCount, &numChannels); 34. CHECK(success); 35. if (mAudioSink.get() != NULL) { 36. status_t err = mAudioSink->open( 37. mSampleRate, numChannels, AudioSystem::PCM_16_BIT, 38. DEFAULT_AUDIOSINK_BUFFERCOUNT, 39. &AudioPlayer::AudioSinkCallback, this); 40. if (err != OK) { 41. if (mFirstBuffer != NULL) { 42. mFirstBuffer->release(); 43. mFirstBuffer = NULL; 44. } 45. if (!sourceAlreadyStarted) { 46. mSource->stop(); 47. } 48. return err; 49. } 50. mLatencyUs = (int64_t)mAudioSink->latency() * 1000; 51. mFrameSize = mAudioSink->frameSize(); 52. mAudioSink->start(); 53. } else { 54. mAudioTrack = new AudioTrack( 55. AudioSystem::MUSIC, mSampleRate, AudioSystem::PCM_16_BIT, 56. (numChannels == 2) 57. ? AudioSystem::CHANNEL_OUT_STEREO 58. : AudioSystem::CHANNEL_OUT_MONO, 59. 0, 0, &AudioCallback, this, 0); 60. 61. if ((err = mAudioTrack->initCheck()) != OK) { 62. delete mAudioTrack; 63. mAudioTrack = NULL; 64. 65. if (mFirstBuffer != NULL) { 66. mFirstBuffer->release(); 67. mFirstBuffer = NULL; 68. } 69. if (!sourceAlreadyStarted) { 70. mSource->stop(); 71. } 72. return err; 73. } 74. mLatencyUs = (int64_t)mAudioTrack->latency() * 1000; 75. mFrameSize = mAudioTrack->frameSize(); 76. mAudioTrack->start(); 77. } 78. mStarted = true; 79. return OK; 80. } 这里代码主要部分如下:调用mSource->read 启动解码,在OMXCodec一篇的介绍中知道,解码第一帧相当于启动了解码循环获取音频参数:采样率、声道数、以及量化位数(这里只支持PCM_16_BIT)启动输出:这里若mAudioSink非空,则启动mAudioSink进行输出,否则构造一个AudioTrack进行音频输出,这里AudioTrack是比较底层的接口 AudioOut是AudioTrack的封装。在start方法中主要是调用mAudioSink进行工作,主要代码为status_t err = mAudioSink->open(mSampleRate, numChannels, channelMask, AUDIO_FORMAT_PCM_16_BIT,DEFAULT_AUDIOSINK_BUFFERCOUNT,&AudioPlayer::AudioSinkCallback,this,(mAllowDeepBuffering ?AUDIO_OUTPUT_FLAG_DEEP_BUFFER :AUDIO_OUTPUT_FLAG_NONE));mAudioSink->start();之前说过mAudioSink是AudioOut对象,看下实际的实现(代码在mediaplayerservice.cpp中)首先mAudioSink->open 需要注意的是传入的参数中有个函数指针 AudioPlayer::AudioSinkCallback ,其主要作用就是audioout播放pcm的时候会定期调用此回调函数填充数据,具体实现如下分段来看代码 [html] view plaincopy1. status_t MediaPlayerService::AudioOutput::open( 2. uint32_t sampleRate, int channelCount, audio_channel_mask_t channelMask, 3. audio_format_t format, int bufferCount, 4. AudioCallback cb, void *cookie, 5. audio_output_flags_t flags) 6. { 7. mCallback = cb; 8. mCallbackCookie = cookie; 9. 10. // Check argument "bufferCount" against the mininum buffer count 11. if (bufferCount < mMinBufferCount) { 12. ALOGD("bufferCount (%d) is too small and increased to %d", bufferCount, mMinBufferCount); 13. bufferCount = mMinBufferCount; 14. 15. } 16. ALOGV("open(%u, %d, 0x%x, %d, %d, %d)", sampleRate, channelCount, channelMask, 17. format, bufferCount, mSessionId); 18. int afSampleRate; 19. int afFrameCount; 20. uint32_t frameCount; 21. 22. if (AudioSystem::getOutputFrameCount(&afFrameCount, mStreamType) != NO_ERROR) { 23. return NO_INIT; 24. } 25. if (AudioSystem::getOutputSamplingRate(&afSampleRate, mStreamType) != NO_ERROR) { 26. return NO_INIT; 27. } 28. 29. frameCount = (sampleRate*afFrameCount*bufferCount)/afSampleRate; 30. 31. if (channelMask == CHANNEL_MASK_USE_CHANNEL_ORDER) { 32. channelMask = audio_channel_out_mask_from_count(channelCount); 33. if (0 == channelMask) { 34. ALOGE("open() error, can\'t derive mask for %d audio channels", channelCount); 35. return NO_INIT; 36. } 37. } 上述代码主要就是处理传入的参数,回调函数保存在 mCallback中, cookie代表的是AudioPlayer对象指针后面是根据采样率声道数等计算 frameCount等 [html] view plaincopy1. AudioTrack *t; 2. CallbackData *newcbd = NULL; 3. if (mCallback != NULL) { 4. newcbd = new CallbackData(this); 5. t = new AudioTrack( 6. mStreamType, 7. sampleRate, 8. format, 9. channelMask, 10. frameCount, 11. flags, 12. CallbackWrapper, 13. newcbd, 14. 0, // notification frames 15. mSessionId); 16. } else { 17. t = new AudioTrack( 18. mStreamType, 19. sampleRate, 20. format, 21. channelMask, 22. frameCount, 23. flags, 24. NULL, 25. NULL, 26. 0, 27. mSessionId); 28. } 上面是构造AudioTrack对象,之前介绍实际的playback接口是通过封装AudioTrack来实现的。 [html] view plaincopy1. mCallbackData = newcbd; 2. ALOGV("setVolume"); 3. t->setVolume(mLeftVolume, mRightVolume); 4. 5. mSampleRateHz = sampleRate; 6. mFlags = flags; 7. mMsecsPerFrame = mPlaybackRatePermille / (float) sampleRate; 8. uint32_t pos; 9. if (t->getPosition(&pos) == OK) { 10. mBytesWritten = uint64_t(pos) * t->frameSize(); 11. } 12. mTrack = t; 13. 14. status_t res = t->setSampleRate(mPlaybackRatePermille * mSampleRateHz / 1000); 15. if (res != NO_ERROR) { 16. return res; 17. } 18. t->setAuxEffectSendLevel(mSendLevel); 19. return t->attachAuxEffect(mAuxEffectId);; 20. } 最后是将audiotrack对象存储在mTrack成员中。这里还有个重要的细节要注意,在构造AudioTrack对象的时候,传入了CallbackWrapper作为audiotrack的callback当audiotrack需要数据的时候,就会调用此函数,看下实现 [html] view plaincopy1. void MediaPlayerService::AudioOutput::CallbackWrapper( 2. int event, void *cookie, void *info) { 3. //ALOGV("callbackwrapper"); 4. if (event != AudioTrack::EVENT_MORE_DATA) { 5. return; 6. } 7. CallbackData *data = (CallbackData*)cookie; 8. AudioOutput *me = data->getOutput(); 9. size_t actualSize = (*me->mCallback)( 10. me, buffer->raw, buffer->size, me->mCallbackCookie); 11. } 只列出了重要的代码,当event ==EVENT_MORE_DATA即需要更多的数据,则调用 mCallback来填充数据此处mCallback就是open传入的AudioSinkCallback函数指针,代码如下 [html] view plaincopy1. size_t AudioPlayer::AudioSinkCallback( 2. MediaPlayerBase::AudioSink *audioSink, 3. void *buffer, size_t size, void *cookie) { 4. AudioPlayer *me = (AudioPlayer *)cookie; 5. 6. return me->fillBuffer(buffer, size); 7. } 因此实际调用的是fillBuffer,这里不再列出fillbuffer的代码了,核心的语句就一句err = mSource->read(&mInputBuffer, &options);即调用解码器的mediabuffer来填充数据。这里还有些播放进度的操作,后面介绍同步的时候再看。最后看下start [html] view plaincopy1. void MediaPlayerService::AudioOutput::start() 2. { 3. ALOGV("start"); 4. if (mCallbackData != NULL) { 5. mCallbackData->endTrackSwitch(); 6. } 7. if (mTrack) { 8. mTrack->setVolume(mLeftVolume, mRightVolume); 9. mTrack->setAuxEffectSendLevel(mSendLevel); 10. mTrack->start(); 11. } 12. } 比较简单就是只掉调用mTrack->start, audiotrack启动后就会周期性的调用 回调函数从解码器获取数据。2 时间戳计算--------------------------------------------------------------------------------这里主要是讲audioplayer如何更新时间戳,在awesomeplayer中会使用audio的时间戳来做同步之前讲过在awesomeplayer中,通过onVideoEvent来驱动整个整个播放的进度,其中有如下代码 [html] view plaincopy1. TimeSource *ts = 2. ((mFlags & AUDIO_AT_EOS) || !(mFlags & AUDIOPLAYER_STARTED)) 3. ? &mSystemTimeSource : mTimeSource; 4. 5. if (mFlags & FIRST_FRAME) { 6. modifyFlags(FIRST_FRAME, CLEAR); 7. mSinceLastDropped = 0; 8. mTimeSourceDeltaUs = ts->getRealTimeUs() - timeUs; 9. } 10. 11. int64_t realTimeUs, mediaTimeUs; 12. if (!(mFlags & AUDIO_AT_EOS) && mAudioPlayer != NULL 13. && mAudioPlayer->getMediaTimeMapping(&realTimeUs, &mediaTimeUs)) { 14. mTimeSourceDeltaUs = realTimeUs - mediaTimeUs; 15. } 16. 17. int64_t nowUs = ts->getRealTimeUs() - mTimeSourceDeltaUs; 18. 19. int64_t latenessUs = nowUs - timeUs; 20. 21. ====================== 22. 23. if (latenessUs > 500000ll 24. && mAudioPlayer != NULL 25. && mAudioPlayer->getMediaTimeMapping( 26. &realTimeUs, &mediaTimeUs)) { 27. if (mWVMExtractor == NULL) { 28. ALOGI("we're much too late (%.2f secs), video skipping ahead", 29. latenessUs / 1E6); 30. 31. mVideoBuffer->release(); 32. mVideoBuffer = NULL; 33. 34. mSeeking = SEEK_VIDEO_ONLY; 35. mSeekTimeUs = mediaTimeUs; 36. 37. postVideoEvent_l(); 38. return; 39. } else { 40. // The widevine extractor doesn't deal well with seeking 41. // audio and video independently. We'll just have to wait 42. // until the decoder catches up, which won't be long at all. 43. ALOGI("we're very late (%.2f secs)", latenessUs / 1E6); 44. } 45. } 46. 47. if (latenessUs < -10000) { 48. // We're more than 10ms early. 49. postVideoEvent_l(10000); 50. return; 51. } 这里主要就是 == 上面的部分,用于计算latenessUs,后面依据此变量的值来决定是丢帧还是快播 慢播等操作。 == 下面的代码是实际的响应代码主要就是上半部分代码。在构造audioplayer的时候会执行如下语句mTimeSource = mAudioPlayer;即将AudioPlayer作为参考时钟,上述代码中timeUs是由如下语句获得:CHECK(mVideoBuffer->meta_data()->findInt64(kKeyTime, &timeUs));即timeUs 表示下一帧画面的时间戳而mTimeSourceDeltaUs = ts->getRealTimeUs() - timeUs; 当显示画面是第一帧时,表示当前的audio的播放时间与第一帧video的时间差值下面mTimeSourceDeltaUs = realTimeUs - mediaTimeUs; 而其中变量又是通过mAudioPlayer->getMediaTimeMapping(&realTimeUs, &mediaTimeUs)得到【问题】此处会覆盖第一帧的结果,难道只有video的时候要特殊处理吗,知道的普及下。觉得此处没啥用这里看下realTimeUs 和 mediaTimeUs 的计算方式实际计算代码如下 [html] view plaincopy1. bool AudioPlayer::getMediaTimeMapping( 2. int64_t *realtime_us, int64_t *mediatime_us) { 3. Mutex::Autolock autoLock(mLock); 4. 5. *realtime_us = mPositionTimeRealUs; 6. *mediatime_us = mPositionTimeMediaUs; 7. 8. return mPositionTimeRealUs != -1 && mPositionTimeMediaUs != -1; 9. } 因此实际的值分别是mPositionTimeRealUs 和mPositionTimeMediaUs在AudioPlayer中主要是通过fillbuffer来更新此两个成员的值,看下实现 [html] view plaincopy1. size_t AudioPlayer::fillBuffer(data, size) 2. { 3. ... 4. mSource->read(&mInputBuffer, ...); 5. mInputBuffer->meta_data()->findInt64(kKeyTime, &mPositionTimeMediaUs); 6. mPositionTimeRealUs = ((mNumFramesPlayed + size_done / mFrameSize) * 1000000) / mSampleRate; 7. ... 8. } 从代码可以看出 mPositionTimeMediaUs 是当前播放的pcm一包数据的开头的时间戳而mPositionTimeRealUs 是依据当前读取了多少数据计算出来的更精确的时间。二者的差值表示这一包pcm数据已经播放了多少。最后通过如下语句计算int64_t nowUs = ts->getRealTimeUs() - mTimeSourceDeltaUs;这里ts->getRealTimeUs() 得到的时间是将后面audiotrack的缓冲区也考虑进来得到的实际播放时间。nowUs 可以认为是是当前读到的pcm包的开头的时间戳int64_t latenessUs = nowUs - timeUs;这里是将 pcm包开头的时间戳与视频帧的时间戳对比得到二者之间的差值来做同步这里需要注意的是:a-v 时间戳对比的是audio包头的差值,而不是用audio的实际播放进度来计算的 (这样可能不太准吧)3 Seek功能--------------------------------------------------------------------------------之前在研究extractor以及decoder的时候由于一些细节不了解,对seek没有分析,在这里简单分析一下。当seek命令到来时 [html] view plaincopy1. status_t AwesomePlayer::seekTo_l(int64_t timeUs) { 2. if ((mFlags & PLAYING) && mVideoSource != NULL && (mFlags & VIDEO_AT_EOS)) { 3. // Video playback completed before, there's no pending 4. // video event right now. In order for this new seek 5. // to be honored, we need to post one. 6. 7. postVideoEvent_l(); 8. } 9. 10. mSeeking = SEEK; 11. mSeekNotificationSent = false; 12. mSeekTimeUs = timeUs; 13. modifyFlags((AT_EOS | AUDIO_AT_EOS | VIDEO_AT_EOS), CLEAR); 14. 15. seekAudioIfNecessary_l(); 16. return OK; 17. } 在awesomeplayer中主要就是设置seek 标志及seek后的时间对于extractor,这里还是以ts为例,在read的时候 [html] view plaincopy1. status_t MPEG2TSSource::read( 2. MediaBuffer **out, const ReadOptions *options) { 3. *out = NULL; 4. int64_t seekTimeUs; 5. ReadOptions::SeekMode seekMode; 6. if (mSeekable && options && options->getSeekTo(&seekTimeUs, &seekMode)) { 7. mExtractor->seekTo(seekTimeUs); 8. } 9. status_t finalResult; 10. while (!mImpl->hasBufferAvailable(&finalResult)) { 11. if (finalResult != OK) { 12. return ERROR_END_OF_STREAM; 13. } 14. status_t err = mExtractor->feedMore(); 15. if (err != OK) { 16. mImpl->signalEOS(err); 17. } 18. } 19. return mImpl->read(out, options); 20. } 如果需要seek则首先会调用mExtractor->seekTo(seekTimeUs); seek到对应的时间的位置然后下一包数据就是seek后的数据了而对于decoder来讲,读取数据在omxcodec中 [html] view plaincopy1. status_t OMXCodec::read() 2. 3. { 4. ********** 5. if (seeking) { 6. CODEC_LOGV("seeking to %lld us (%.2f secs)", seekTimeUs, seekTimeUs / 1E6); 7. mSignalledEOS = false; 8. CHECK(seekTimeUs >= 0); 9. mSeekTimeUs = seekTimeUs; 10. mSeekMode = seekMode; 11. mFilledBuffers.clear(); 12. CHECK_EQ((int)mState, (int)EXECUTING); 13. bool emulateInputFlushCompletion = !flushPortAsync(kPortIndexInput); 14. bool emulateOutputFlushCompletion = !flushPortAsync(kPortIndexOutput); 15. if (emulateInputFlushCompletion) { 16. onCmdComplete(OMX_CommandFlush, kPortIndexInput); 17. } 18. if (emulateOutputFlushCompletion) { 19. onCmdComplete(OMX_CommandFlush, kPortIndexOutput); 20. } 21. 22. while (mSeekTimeUs >= 0) { 23. if ((err = waitForBufferFilled_l()) != OK) { 24. return err; 25. } 26. } 27. } 28. ********* 29. 30. } 这里可以看到,当需要seek的时候会首先清空所有已经读到的数据。然后等待extractor填充数据。填充完毕后就seek完毕了。【结束】
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值