Fri, 16 Jan 2015 04:50:19 +0100
Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
michael@0 | 3 | /* |
michael@0 | 4 | * Copyright (c) 2014 The Linux Foundation. All rights reserved. |
michael@0 | 5 | * Copyright (C) 2009 The Android Open Source Project |
michael@0 | 6 | * |
michael@0 | 7 | * Licensed under the Apache License, Version 2.0 (the "License"); |
michael@0 | 8 | * you may not use this file except in compliance with the License. |
michael@0 | 9 | * You may obtain a copy of the License at |
michael@0 | 10 | * |
michael@0 | 11 | * http://www.apache.org/licenses/LICENSE-2.0 |
michael@0 | 12 | * |
michael@0 | 13 | * Unless required by applicable law or agreed to in writing, software |
michael@0 | 14 | * distributed under the License is distributed on an "AS IS" BASIS, |
michael@0 | 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
michael@0 | 16 | * See the License for the specific language governing permissions and |
michael@0 | 17 | * limitations under the License. |
michael@0 | 18 | */ |
michael@0 | 19 | |
michael@0 | 20 | #include "AudioOffloadPlayer.h" |
michael@0 | 21 | #include "nsComponentManagerUtils.h" |
michael@0 | 22 | #include "nsITimer.h" |
michael@0 | 23 | #include "mozilla/dom/HTMLMediaElement.h" |
michael@0 | 24 | |
michael@0 | 25 | #include <binder/IPCThreadState.h> |
michael@0 | 26 | #include <stagefright/foundation/ADebug.h> |
michael@0 | 27 | #include <stagefright/foundation/ALooper.h> |
michael@0 | 28 | #include <stagefright/MediaDefs.h> |
michael@0 | 29 | #include <stagefright/MediaErrors.h> |
michael@0 | 30 | #include <stagefright/MediaSource.h> |
michael@0 | 31 | #include <stagefright/MetaData.h> |
michael@0 | 32 | #include <stagefright/Utils.h> |
michael@0 | 33 | #include <AudioTrack.h> |
michael@0 | 34 | #include <AudioSystem.h> |
michael@0 | 35 | #include <AudioParameter.h> |
michael@0 | 36 | #include <hardware/audio.h> |
michael@0 | 37 | |
michael@0 | 38 | using namespace android; |
michael@0 | 39 | |
michael@0 | 40 | namespace mozilla { |
michael@0 | 41 | |
michael@0 | 42 | #ifdef PR_LOGGING |
michael@0 | 43 | PRLogModuleInfo* gAudioOffloadPlayerLog; |
michael@0 | 44 | #define AUDIO_OFFLOAD_LOG(type, msg) \ |
michael@0 | 45 | PR_LOG(gAudioOffloadPlayerLog, type, msg) |
michael@0 | 46 | #else |
michael@0 | 47 | #define AUDIO_OFFLOAD_LOG(type, msg) |
michael@0 | 48 | #endif |
michael@0 | 49 | |
michael@0 | 50 | // maximum time in paused state when offloading audio decompression. |
michael@0 | 51 | // When elapsed, the AudioSink is destroyed to allow the audio DSP to power down. |
michael@0 | 52 | static const uint64_t OFFLOAD_PAUSE_MAX_MSECS = 60000ll; |
michael@0 | 53 | |
michael@0 | 54 | AudioOffloadPlayer::AudioOffloadPlayer(MediaOmxDecoder* aObserver) : |
michael@0 | 55 | mObserver(aObserver), |
michael@0 | 56 | mInputBuffer(nullptr), |
michael@0 | 57 | mSampleRate(0), |
michael@0 | 58 | mSeeking(false), |
michael@0 | 59 | mSeekDuringPause(false), |
michael@0 | 60 | mReachedEOS(false), |
michael@0 | 61 | mSeekTimeUs(0), |
michael@0 | 62 | mStartPosUs(0), |
michael@0 | 63 | mPositionTimeMediaUs(-1), |
michael@0 | 64 | mStarted(false), |
michael@0 | 65 | mPlaying(false), |
michael@0 | 66 | mIsElementVisible(true) |
michael@0 | 67 | { |
michael@0 | 68 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 69 | |
michael@0 | 70 | #ifdef PR_LOGGING |
michael@0 | 71 | if (!gAudioOffloadPlayerLog) { |
michael@0 | 72 | gAudioOffloadPlayerLog = PR_NewLogModule("AudioOffloadPlayer"); |
michael@0 | 73 | } |
michael@0 | 74 | #endif |
michael@0 | 75 | |
michael@0 | 76 | CHECK(aObserver); |
michael@0 | 77 | mSessionId = AudioSystem::newAudioSessionId(); |
michael@0 | 78 | AudioSystem::acquireAudioSessionId(mSessionId); |
michael@0 | 79 | mAudioSink = new AudioOutput(mSessionId, |
michael@0 | 80 | IPCThreadState::self()->getCallingUid()); |
michael@0 | 81 | } |
michael@0 | 82 | |
michael@0 | 83 | AudioOffloadPlayer::~AudioOffloadPlayer() |
michael@0 | 84 | { |
michael@0 | 85 | Reset(); |
michael@0 | 86 | AudioSystem::releaseAudioSessionId(mSessionId); |
michael@0 | 87 | } |
michael@0 | 88 | |
michael@0 | 89 | void AudioOffloadPlayer::SetSource(const sp<MediaSource> &aSource) |
michael@0 | 90 | { |
michael@0 | 91 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 92 | CHECK(!mSource.get()); |
michael@0 | 93 | |
michael@0 | 94 | mSource = aSource; |
michael@0 | 95 | } |
michael@0 | 96 | |
michael@0 | 97 | status_t AudioOffloadPlayer::Start(bool aSourceAlreadyStarted) |
michael@0 | 98 | { |
michael@0 | 99 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 100 | CHECK(!mStarted); |
michael@0 | 101 | CHECK(mSource.get()); |
michael@0 | 102 | |
michael@0 | 103 | status_t err; |
michael@0 | 104 | CHECK(mAudioSink.get()); |
michael@0 | 105 | |
michael@0 | 106 | if (!aSourceAlreadyStarted) { |
michael@0 | 107 | err = mSource->start(); |
michael@0 | 108 | |
michael@0 | 109 | if (err != OK) { |
michael@0 | 110 | return err; |
michael@0 | 111 | } |
michael@0 | 112 | } |
michael@0 | 113 | |
michael@0 | 114 | sp<MetaData> format = mSource->getFormat(); |
michael@0 | 115 | const char* mime; |
michael@0 | 116 | int avgBitRate = -1; |
michael@0 | 117 | int32_t channelMask; |
michael@0 | 118 | int32_t numChannels; |
michael@0 | 119 | int64_t durationUs = -1; |
michael@0 | 120 | audio_format_t audioFormat = AUDIO_FORMAT_PCM_16_BIT; |
michael@0 | 121 | uint32_t flags = AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD; |
michael@0 | 122 | audio_offload_info_t offloadInfo = AUDIO_INFO_INITIALIZER; |
michael@0 | 123 | |
michael@0 | 124 | CHECK(format->findCString(kKeyMIMEType, &mime)); |
michael@0 | 125 | CHECK(format->findInt32(kKeySampleRate, &mSampleRate)); |
michael@0 | 126 | CHECK(format->findInt32(kKeyChannelCount, &numChannels)); |
michael@0 | 127 | format->findInt32(kKeyBitRate, &avgBitRate); |
michael@0 | 128 | format->findInt64(kKeyDuration, &durationUs); |
michael@0 | 129 | |
michael@0 | 130 | if(!format->findInt32(kKeyChannelMask, &channelMask)) { |
michael@0 | 131 | channelMask = CHANNEL_MASK_USE_CHANNEL_ORDER; |
michael@0 | 132 | } |
michael@0 | 133 | |
michael@0 | 134 | if (mapMimeToAudioFormat(audioFormat, mime) != OK) { |
michael@0 | 135 | AUDIO_OFFLOAD_LOG(PR_LOG_ERROR, ("Couldn't map mime type \"%s\" to a valid " |
michael@0 | 136 | "AudioSystem::audio_format", mime)); |
michael@0 | 137 | audioFormat = AUDIO_FORMAT_INVALID; |
michael@0 | 138 | } |
michael@0 | 139 | |
michael@0 | 140 | offloadInfo.duration_us = durationUs; |
michael@0 | 141 | offloadInfo.sample_rate = mSampleRate; |
michael@0 | 142 | offloadInfo.channel_mask = channelMask; |
michael@0 | 143 | offloadInfo.format = audioFormat; |
michael@0 | 144 | offloadInfo.stream_type = AUDIO_STREAM_MUSIC; |
michael@0 | 145 | offloadInfo.bit_rate = avgBitRate; |
michael@0 | 146 | offloadInfo.has_video = false; |
michael@0 | 147 | offloadInfo.is_streaming = false; |
michael@0 | 148 | |
michael@0 | 149 | AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("isOffloadSupported: SR=%u, CM=0x%x, " |
michael@0 | 150 | "Format=0x%x, StreamType=%d, BitRate=%u, duration=%lld us, has_video=%d", |
michael@0 | 151 | offloadInfo.sample_rate, offloadInfo.channel_mask, offloadInfo.format, |
michael@0 | 152 | offloadInfo.stream_type, offloadInfo.bit_rate, offloadInfo.duration_us, |
michael@0 | 153 | offloadInfo.has_video)); |
michael@0 | 154 | |
michael@0 | 155 | err = mAudioSink->Open(mSampleRate, |
michael@0 | 156 | numChannels, |
michael@0 | 157 | channelMask, |
michael@0 | 158 | audioFormat, |
michael@0 | 159 | &AudioOffloadPlayer::AudioSinkCallback, |
michael@0 | 160 | this, |
michael@0 | 161 | (audio_output_flags_t) flags, |
michael@0 | 162 | &offloadInfo); |
michael@0 | 163 | if (err == OK) { |
michael@0 | 164 | // If the playback is offloaded to h/w we pass the |
michael@0 | 165 | // HAL some metadata information |
michael@0 | 166 | // We don't want to do this for PCM because it will be going |
michael@0 | 167 | // through the AudioFlinger mixer before reaching the hardware |
michael@0 | 168 | SendMetaDataToHal(mAudioSink, format); |
michael@0 | 169 | } |
michael@0 | 170 | mStarted = true; |
michael@0 | 171 | mPlaying = false; |
michael@0 | 172 | |
michael@0 | 173 | return err; |
michael@0 | 174 | } |
michael@0 | 175 | |
michael@0 | 176 | status_t AudioOffloadPlayer::ChangeState(MediaDecoder::PlayState aState) |
michael@0 | 177 | { |
michael@0 | 178 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 179 | mPlayState = aState; |
michael@0 | 180 | |
michael@0 | 181 | switch (mPlayState) { |
michael@0 | 182 | case MediaDecoder::PLAY_STATE_PLAYING: { |
michael@0 | 183 | status_t err = Play(); |
michael@0 | 184 | if (err != OK) { |
michael@0 | 185 | return err; |
michael@0 | 186 | } |
michael@0 | 187 | StartTimeUpdate(); |
michael@0 | 188 | } break; |
michael@0 | 189 | |
michael@0 | 190 | case MediaDecoder::PLAY_STATE_SEEKING: { |
michael@0 | 191 | int64_t seekTimeUs |
michael@0 | 192 | = mObserver->GetSeekTime(); |
michael@0 | 193 | SeekTo(seekTimeUs, true); |
michael@0 | 194 | mObserver->ResetSeekTime(); |
michael@0 | 195 | } break; |
michael@0 | 196 | |
michael@0 | 197 | case MediaDecoder::PLAY_STATE_PAUSED: |
michael@0 | 198 | case MediaDecoder::PLAY_STATE_SHUTDOWN: |
michael@0 | 199 | // Just pause here during play state shutdown as well to stop playing |
michael@0 | 200 | // offload track immediately. Resources will be freed by MediaOmxDecoder |
michael@0 | 201 | Pause(); |
michael@0 | 202 | break; |
michael@0 | 203 | |
michael@0 | 204 | case MediaDecoder::PLAY_STATE_ENDED: |
michael@0 | 205 | Pause(true); |
michael@0 | 206 | break; |
michael@0 | 207 | |
michael@0 | 208 | default: |
michael@0 | 209 | break; |
michael@0 | 210 | } |
michael@0 | 211 | return OK; |
michael@0 | 212 | } |
michael@0 | 213 | |
michael@0 | 214 | static void ResetCallback(nsITimer* aTimer, void* aClosure) |
michael@0 | 215 | { |
michael@0 | 216 | AudioOffloadPlayer* player = static_cast<AudioOffloadPlayer*>(aClosure); |
michael@0 | 217 | if (player) { |
michael@0 | 218 | player->Reset(); |
michael@0 | 219 | } |
michael@0 | 220 | } |
michael@0 | 221 | |
michael@0 | 222 | void AudioOffloadPlayer::Pause(bool aPlayPendingSamples) |
michael@0 | 223 | { |
michael@0 | 224 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 225 | |
michael@0 | 226 | if (mStarted) { |
michael@0 | 227 | CHECK(mAudioSink.get()); |
michael@0 | 228 | if (aPlayPendingSamples) { |
michael@0 | 229 | mAudioSink->Stop(); |
michael@0 | 230 | } else { |
michael@0 | 231 | mAudioSink->Pause(); |
michael@0 | 232 | } |
michael@0 | 233 | mPlaying = false; |
michael@0 | 234 | } |
michael@0 | 235 | |
michael@0 | 236 | if (mResetTimer) { |
michael@0 | 237 | return; |
michael@0 | 238 | } |
michael@0 | 239 | mResetTimer = do_CreateInstance("@mozilla.org/timer;1"); |
michael@0 | 240 | mResetTimer->InitWithFuncCallback(ResetCallback, |
michael@0 | 241 | this, |
michael@0 | 242 | OFFLOAD_PAUSE_MAX_MSECS, |
michael@0 | 243 | nsITimer::TYPE_ONE_SHOT); |
michael@0 | 244 | } |
michael@0 | 245 | |
michael@0 | 246 | status_t AudioOffloadPlayer::Play() |
michael@0 | 247 | { |
michael@0 | 248 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 249 | |
michael@0 | 250 | if (mResetTimer) { |
michael@0 | 251 | mResetTimer->Cancel(); |
michael@0 | 252 | mResetTimer = nullptr; |
michael@0 | 253 | } |
michael@0 | 254 | |
michael@0 | 255 | status_t err = OK; |
michael@0 | 256 | |
michael@0 | 257 | if (!mStarted) { |
michael@0 | 258 | // Last pause timed out and offloaded audio sink was reset. Start it again |
michael@0 | 259 | err = Start(false); |
michael@0 | 260 | if (err != OK) { |
michael@0 | 261 | return err; |
michael@0 | 262 | } |
michael@0 | 263 | // Seek to last play position only when there was no seek during last pause |
michael@0 | 264 | if (!mSeeking) { |
michael@0 | 265 | SeekTo(mPositionTimeMediaUs); |
michael@0 | 266 | } |
michael@0 | 267 | } |
michael@0 | 268 | |
michael@0 | 269 | if (!mPlaying) { |
michael@0 | 270 | CHECK(mAudioSink.get()); |
michael@0 | 271 | err = mAudioSink->Start(); |
michael@0 | 272 | if (err == OK) { |
michael@0 | 273 | mPlaying = true; |
michael@0 | 274 | } |
michael@0 | 275 | } |
michael@0 | 276 | |
michael@0 | 277 | return err; |
michael@0 | 278 | } |
michael@0 | 279 | |
michael@0 | 280 | void AudioOffloadPlayer::Reset() |
michael@0 | 281 | { |
michael@0 | 282 | if (!mStarted) { |
michael@0 | 283 | return; |
michael@0 | 284 | } |
michael@0 | 285 | |
michael@0 | 286 | CHECK(mAudioSink.get()); |
michael@0 | 287 | |
michael@0 | 288 | AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("reset: mPlaying=%d mReachedEOS=%d", |
michael@0 | 289 | mPlaying, mReachedEOS)); |
michael@0 | 290 | |
michael@0 | 291 | mAudioSink->Stop(); |
michael@0 | 292 | // If we're closing and have reached EOS, we don't want to flush |
michael@0 | 293 | // the track because if it is offloaded there could be a small |
michael@0 | 294 | // amount of residual data in the hardware buffer which we must |
michael@0 | 295 | // play to give gapless playback. |
michael@0 | 296 | // But if we're resetting when paused or before we've reached EOS |
michael@0 | 297 | // we can't be doing a gapless playback and there could be a large |
michael@0 | 298 | // amount of data queued in the hardware if the track is offloaded, |
michael@0 | 299 | // so we must flush to prevent a track switch being delayed playing |
michael@0 | 300 | // the buffered data that we don't want now |
michael@0 | 301 | if (!mPlaying || !mReachedEOS) { |
michael@0 | 302 | mAudioSink->Flush(); |
michael@0 | 303 | } |
michael@0 | 304 | |
michael@0 | 305 | mAudioSink->Close(); |
michael@0 | 306 | // Make sure to release any buffer we hold onto so that the |
michael@0 | 307 | // source is able to stop(). |
michael@0 | 308 | |
michael@0 | 309 | if (mInputBuffer) { |
michael@0 | 310 | AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Releasing input buffer")); |
michael@0 | 311 | |
michael@0 | 312 | mInputBuffer->release(); |
michael@0 | 313 | mInputBuffer = nullptr; |
michael@0 | 314 | } |
michael@0 | 315 | mSource->stop(); |
michael@0 | 316 | |
michael@0 | 317 | IPCThreadState::self()->flushCommands(); |
michael@0 | 318 | StopTimeUpdate(); |
michael@0 | 319 | |
michael@0 | 320 | mReachedEOS = false; |
michael@0 | 321 | mStarted = false; |
michael@0 | 322 | mPlaying = false; |
michael@0 | 323 | mStartPosUs = 0; |
michael@0 | 324 | } |
michael@0 | 325 | |
michael@0 | 326 | status_t AudioOffloadPlayer::SeekTo(int64_t aTimeUs, bool aDispatchSeekEvents) |
michael@0 | 327 | { |
michael@0 | 328 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 329 | CHECK(mAudioSink.get()); |
michael@0 | 330 | |
michael@0 | 331 | android::Mutex::Autolock autoLock(mLock); |
michael@0 | 332 | |
michael@0 | 333 | AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("SeekTo ( %lld )", aTimeUs)); |
michael@0 | 334 | |
michael@0 | 335 | mSeeking = true; |
michael@0 | 336 | mReachedEOS = false; |
michael@0 | 337 | mPositionTimeMediaUs = -1; |
michael@0 | 338 | mSeekTimeUs = aTimeUs; |
michael@0 | 339 | mStartPosUs = aTimeUs; |
michael@0 | 340 | mDispatchSeekEvents = aDispatchSeekEvents; |
michael@0 | 341 | |
michael@0 | 342 | if (mDispatchSeekEvents) { |
michael@0 | 343 | nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver, |
michael@0 | 344 | &MediaDecoder::SeekingStarted); |
michael@0 | 345 | NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL); |
michael@0 | 346 | } |
michael@0 | 347 | |
michael@0 | 348 | if (mPlaying) { |
michael@0 | 349 | mAudioSink->Pause(); |
michael@0 | 350 | mAudioSink->Flush(); |
michael@0 | 351 | mAudioSink->Start(); |
michael@0 | 352 | |
michael@0 | 353 | } else { |
michael@0 | 354 | mSeekDuringPause = true; |
michael@0 | 355 | |
michael@0 | 356 | if (mStarted) { |
michael@0 | 357 | mAudioSink->Flush(); |
michael@0 | 358 | } |
michael@0 | 359 | |
michael@0 | 360 | if (mDispatchSeekEvents) { |
michael@0 | 361 | mDispatchSeekEvents = false; |
michael@0 | 362 | AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Fake seek complete during pause")); |
michael@0 | 363 | nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver, |
michael@0 | 364 | &MediaDecoder::SeekingStopped); |
michael@0 | 365 | NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL); |
michael@0 | 366 | } |
michael@0 | 367 | } |
michael@0 | 368 | |
michael@0 | 369 | return OK; |
michael@0 | 370 | } |
michael@0 | 371 | |
michael@0 | 372 | double AudioOffloadPlayer::GetMediaTimeSecs() |
michael@0 | 373 | { |
michael@0 | 374 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 375 | return (static_cast<double>(GetMediaTimeUs()) / |
michael@0 | 376 | static_cast<double>(USECS_PER_S)); |
michael@0 | 377 | } |
michael@0 | 378 | |
michael@0 | 379 | int64_t AudioOffloadPlayer::GetMediaTimeUs() |
michael@0 | 380 | { |
michael@0 | 381 | android::Mutex::Autolock autoLock(mLock); |
michael@0 | 382 | |
michael@0 | 383 | int64_t playPosition = 0; |
michael@0 | 384 | if (mSeeking) { |
michael@0 | 385 | return mSeekTimeUs; |
michael@0 | 386 | } |
michael@0 | 387 | if (!mStarted) { |
michael@0 | 388 | return mPositionTimeMediaUs; |
michael@0 | 389 | } |
michael@0 | 390 | |
michael@0 | 391 | playPosition = GetOutputPlayPositionUs_l(); |
michael@0 | 392 | if (!mReachedEOS) { |
michael@0 | 393 | mPositionTimeMediaUs = playPosition; |
michael@0 | 394 | } |
michael@0 | 395 | |
michael@0 | 396 | return mPositionTimeMediaUs; |
michael@0 | 397 | } |
michael@0 | 398 | |
michael@0 | 399 | int64_t AudioOffloadPlayer::GetOutputPlayPositionUs_l() const |
michael@0 | 400 | { |
michael@0 | 401 | CHECK(mAudioSink.get()); |
michael@0 | 402 | uint32_t playedSamples = 0; |
michael@0 | 403 | |
michael@0 | 404 | mAudioSink->GetPosition(&playedSamples); |
michael@0 | 405 | |
michael@0 | 406 | const int64_t playedUs = (static_cast<int64_t>(playedSamples) * 1000000 ) / |
michael@0 | 407 | mSampleRate; |
michael@0 | 408 | |
michael@0 | 409 | // HAL position is relative to the first buffer we sent at mStartPosUs |
michael@0 | 410 | const int64_t renderedDuration = mStartPosUs + playedUs; |
michael@0 | 411 | return renderedDuration; |
michael@0 | 412 | } |
michael@0 | 413 | |
michael@0 | 414 | void AudioOffloadPlayer::NotifyAudioEOS() |
michael@0 | 415 | { |
michael@0 | 416 | nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver, |
michael@0 | 417 | &MediaDecoder::PlaybackEnded); |
michael@0 | 418 | NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL); |
michael@0 | 419 | } |
michael@0 | 420 | |
michael@0 | 421 | void AudioOffloadPlayer::NotifyPositionChanged() |
michael@0 | 422 | { |
michael@0 | 423 | nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver, |
michael@0 | 424 | &MediaOmxDecoder::PlaybackPositionChanged); |
michael@0 | 425 | NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL); |
michael@0 | 426 | } |
michael@0 | 427 | |
michael@0 | 428 | void AudioOffloadPlayer::NotifyAudioTearDown() |
michael@0 | 429 | { |
michael@0 | 430 | nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver, |
michael@0 | 431 | &MediaOmxDecoder::AudioOffloadTearDown); |
michael@0 | 432 | NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL); |
michael@0 | 433 | } |
michael@0 | 434 | |
michael@0 | 435 | // static |
michael@0 | 436 | size_t AudioOffloadPlayer::AudioSinkCallback(AudioSink* aAudioSink, |
michael@0 | 437 | void* aBuffer, |
michael@0 | 438 | size_t aSize, |
michael@0 | 439 | void* aCookie, |
michael@0 | 440 | AudioSink::cb_event_t aEvent) |
michael@0 | 441 | { |
michael@0 | 442 | AudioOffloadPlayer* me = (AudioOffloadPlayer*) aCookie; |
michael@0 | 443 | |
michael@0 | 444 | switch (aEvent) { |
michael@0 | 445 | |
michael@0 | 446 | case AudioSink::CB_EVENT_FILL_BUFFER: |
michael@0 | 447 | AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Notify Audio position changed")); |
michael@0 | 448 | me->NotifyPositionChanged(); |
michael@0 | 449 | return me->FillBuffer(aBuffer, aSize); |
michael@0 | 450 | |
michael@0 | 451 | case AudioSink::CB_EVENT_STREAM_END: |
michael@0 | 452 | AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Notify Audio EOS")); |
michael@0 | 453 | me->mReachedEOS = true; |
michael@0 | 454 | me->NotifyAudioEOS(); |
michael@0 | 455 | break; |
michael@0 | 456 | |
michael@0 | 457 | case AudioSink::CB_EVENT_TEAR_DOWN: |
michael@0 | 458 | AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Notify Tear down event")); |
michael@0 | 459 | me->NotifyAudioTearDown(); |
michael@0 | 460 | break; |
michael@0 | 461 | |
michael@0 | 462 | default: |
michael@0 | 463 | AUDIO_OFFLOAD_LOG(PR_LOG_ERROR, ("Unknown event %d from audio sink", |
michael@0 | 464 | aEvent)); |
michael@0 | 465 | break; |
michael@0 | 466 | } |
michael@0 | 467 | return 0; |
michael@0 | 468 | } |
michael@0 | 469 | |
michael@0 | 470 | size_t AudioOffloadPlayer::FillBuffer(void* aData, size_t aSize) |
michael@0 | 471 | { |
michael@0 | 472 | CHECK(mAudioSink.get()); |
michael@0 | 473 | |
michael@0 | 474 | if (mReachedEOS) { |
michael@0 | 475 | return 0; |
michael@0 | 476 | } |
michael@0 | 477 | |
michael@0 | 478 | size_t sizeDone = 0; |
michael@0 | 479 | size_t sizeRemaining = aSize; |
michael@0 | 480 | while (sizeRemaining > 0) { |
michael@0 | 481 | MediaSource::ReadOptions options; |
michael@0 | 482 | bool refreshSeekTime = false; |
michael@0 | 483 | |
michael@0 | 484 | { |
michael@0 | 485 | android::Mutex::Autolock autoLock(mLock); |
michael@0 | 486 | |
michael@0 | 487 | if (mSeeking) { |
michael@0 | 488 | options.setSeekTo(mSeekTimeUs); |
michael@0 | 489 | refreshSeekTime = true; |
michael@0 | 490 | |
michael@0 | 491 | if (mInputBuffer) { |
michael@0 | 492 | mInputBuffer->release(); |
michael@0 | 493 | mInputBuffer = nullptr; |
michael@0 | 494 | } |
michael@0 | 495 | mSeeking = false; |
michael@0 | 496 | } |
michael@0 | 497 | } |
michael@0 | 498 | |
michael@0 | 499 | if (!mInputBuffer) { |
michael@0 | 500 | |
michael@0 | 501 | status_t err; |
michael@0 | 502 | err = mSource->read(&mInputBuffer, &options); |
michael@0 | 503 | |
michael@0 | 504 | CHECK((!err && mInputBuffer) || (err && !mInputBuffer)); |
michael@0 | 505 | |
michael@0 | 506 | android::Mutex::Autolock autoLock(mLock); |
michael@0 | 507 | |
michael@0 | 508 | if (err != OK) { |
michael@0 | 509 | AUDIO_OFFLOAD_LOG(PR_LOG_ERROR, ("Error while reading media source %d " |
michael@0 | 510 | "Ok to receive EOS error at end", err)); |
michael@0 | 511 | if (!mReachedEOS) { |
michael@0 | 512 | // After seek there is a possible race condition if |
michael@0 | 513 | // OffloadThread is observing state_stopping_1 before |
michael@0 | 514 | // framesReady() > 0. Ensure sink stop is called |
michael@0 | 515 | // after last buffer is released. This ensures the |
michael@0 | 516 | // partial buffer is written to the driver before |
michael@0 | 517 | // stopping one is observed.The drawback is that |
michael@0 | 518 | // there will be an unnecessary call to the parser |
michael@0 | 519 | // after parser signalled EOS. |
michael@0 | 520 | if (sizeDone > 0) { |
michael@0 | 521 | AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("send Partial buffer down")); |
michael@0 | 522 | AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("skip calling stop till next" |
michael@0 | 523 | " fillBuffer")); |
michael@0 | 524 | break; |
michael@0 | 525 | } |
michael@0 | 526 | // no more buffers to push - stop() and wait for STREAM_END |
michael@0 | 527 | // don't set mReachedEOS until stream end received |
michael@0 | 528 | mAudioSink->Stop(); |
michael@0 | 529 | } |
michael@0 | 530 | break; |
michael@0 | 531 | } |
michael@0 | 532 | |
michael@0 | 533 | if(mInputBuffer->range_length() != 0) { |
michael@0 | 534 | CHECK(mInputBuffer->meta_data()->findInt64( |
michael@0 | 535 | kKeyTime, &mPositionTimeMediaUs)); |
michael@0 | 536 | } |
michael@0 | 537 | |
michael@0 | 538 | if (refreshSeekTime) { |
michael@0 | 539 | |
michael@0 | 540 | if (mDispatchSeekEvents && !mSeekDuringPause) { |
michael@0 | 541 | mDispatchSeekEvents = false; |
michael@0 | 542 | AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("FillBuffer posting SEEK_COMPLETE")); |
michael@0 | 543 | nsCOMPtr<nsIRunnable> nsEvent = NS_NewRunnableMethod(mObserver, |
michael@0 | 544 | &MediaDecoder::SeekingStopped); |
michael@0 | 545 | NS_DispatchToMainThread(nsEvent, NS_DISPATCH_NORMAL); |
michael@0 | 546 | |
michael@0 | 547 | } else if (mSeekDuringPause) { |
michael@0 | 548 | // Callback is already called for seek during pause. Just reset the |
michael@0 | 549 | // flag |
michael@0 | 550 | AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Not posting seek complete as its" |
michael@0 | 551 | " already faked")); |
michael@0 | 552 | mSeekDuringPause = false; |
michael@0 | 553 | } |
michael@0 | 554 | |
michael@0 | 555 | NotifyPositionChanged(); |
michael@0 | 556 | |
michael@0 | 557 | // need to adjust the mStartPosUs for offload decoding since parser |
michael@0 | 558 | // might not be able to get the exact seek time requested. |
michael@0 | 559 | mStartPosUs = mPositionTimeMediaUs; |
michael@0 | 560 | AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Adjust seek time to: %.2f", |
michael@0 | 561 | mStartPosUs / 1E6)); |
michael@0 | 562 | |
michael@0 | 563 | // clear seek time with mLock locked and once we have valid |
michael@0 | 564 | // mPositionTimeMediaUs |
michael@0 | 565 | // before clearing mSeekTimeUs check if a new seek request has been |
michael@0 | 566 | // received while we were reading from the source with mLock released. |
michael@0 | 567 | if (!mSeeking) { |
michael@0 | 568 | mSeekTimeUs = 0; |
michael@0 | 569 | } |
michael@0 | 570 | } |
michael@0 | 571 | } |
michael@0 | 572 | |
michael@0 | 573 | if (mInputBuffer->range_length() == 0) { |
michael@0 | 574 | mInputBuffer->release(); |
michael@0 | 575 | mInputBuffer = nullptr; |
michael@0 | 576 | continue; |
michael@0 | 577 | } |
michael@0 | 578 | |
michael@0 | 579 | size_t copy = sizeRemaining; |
michael@0 | 580 | if (copy > mInputBuffer->range_length()) { |
michael@0 | 581 | copy = mInputBuffer->range_length(); |
michael@0 | 582 | } |
michael@0 | 583 | |
michael@0 | 584 | memcpy((char *)aData + sizeDone, |
michael@0 | 585 | (const char *)mInputBuffer->data() + mInputBuffer->range_offset(), |
michael@0 | 586 | copy); |
michael@0 | 587 | |
michael@0 | 588 | mInputBuffer->set_range(mInputBuffer->range_offset() + copy, |
michael@0 | 589 | mInputBuffer->range_length() - copy); |
michael@0 | 590 | |
michael@0 | 591 | sizeDone += copy; |
michael@0 | 592 | sizeRemaining -= copy; |
michael@0 | 593 | } |
michael@0 | 594 | return sizeDone; |
michael@0 | 595 | } |
michael@0 | 596 | |
michael@0 | 597 | void AudioOffloadPlayer::SetElementVisibility(bool aIsVisible) |
michael@0 | 598 | { |
michael@0 | 599 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 600 | mIsElementVisible = aIsVisible; |
michael@0 | 601 | if (mIsElementVisible) { |
michael@0 | 602 | AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("Element is visible. Start time update")); |
michael@0 | 603 | StartTimeUpdate(); |
michael@0 | 604 | } |
michael@0 | 605 | } |
michael@0 | 606 | |
michael@0 | 607 | static void TimeUpdateCallback(nsITimer* aTimer, void* aClosure) |
michael@0 | 608 | { |
michael@0 | 609 | AudioOffloadPlayer* player = static_cast<AudioOffloadPlayer*>(aClosure); |
michael@0 | 610 | player->TimeUpdate(); |
michael@0 | 611 | } |
michael@0 | 612 | |
michael@0 | 613 | void AudioOffloadPlayer::TimeUpdate() |
michael@0 | 614 | { |
michael@0 | 615 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 616 | TimeStamp now = TimeStamp::Now(); |
michael@0 | 617 | |
michael@0 | 618 | // If TIMEUPDATE_MS has passed since the last fire update event fired, fire |
michael@0 | 619 | // another timeupdate event. |
michael@0 | 620 | if ((mLastFireUpdateTime.IsNull() || |
michael@0 | 621 | now - mLastFireUpdateTime >= |
michael@0 | 622 | TimeDuration::FromMilliseconds(TIMEUPDATE_MS))) { |
michael@0 | 623 | mLastFireUpdateTime = now; |
michael@0 | 624 | NotifyPositionChanged(); |
michael@0 | 625 | } |
michael@0 | 626 | |
michael@0 | 627 | if (mPlayState != MediaDecoder::PLAY_STATE_PLAYING || !mIsElementVisible) { |
michael@0 | 628 | StopTimeUpdate(); |
michael@0 | 629 | } |
michael@0 | 630 | } |
michael@0 | 631 | |
michael@0 | 632 | nsresult AudioOffloadPlayer::StartTimeUpdate() |
michael@0 | 633 | { |
michael@0 | 634 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 635 | if (mTimeUpdateTimer) { |
michael@0 | 636 | return NS_OK; |
michael@0 | 637 | } |
michael@0 | 638 | |
michael@0 | 639 | mTimeUpdateTimer = do_CreateInstance("@mozilla.org/timer;1"); |
michael@0 | 640 | return mTimeUpdateTimer->InitWithFuncCallback(TimeUpdateCallback, |
michael@0 | 641 | this, |
michael@0 | 642 | TIMEUPDATE_MS, |
michael@0 | 643 | nsITimer::TYPE_REPEATING_SLACK); |
michael@0 | 644 | } |
michael@0 | 645 | |
michael@0 | 646 | nsresult AudioOffloadPlayer::StopTimeUpdate() |
michael@0 | 647 | { |
michael@0 | 648 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 649 | if (!mTimeUpdateTimer) { |
michael@0 | 650 | return NS_OK; |
michael@0 | 651 | } |
michael@0 | 652 | |
michael@0 | 653 | nsresult rv = mTimeUpdateTimer->Cancel(); |
michael@0 | 654 | mTimeUpdateTimer = nullptr; |
michael@0 | 655 | return rv; |
michael@0 | 656 | } |
michael@0 | 657 | |
michael@0 | 658 | MediaDecoderOwner::NextFrameStatus AudioOffloadPlayer::GetNextFrameStatus() |
michael@0 | 659 | { |
michael@0 | 660 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 661 | if (mPlayState == MediaDecoder::PLAY_STATE_SEEKING) { |
michael@0 | 662 | return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING; |
michael@0 | 663 | } else if (mPlayState == MediaDecoder::PLAY_STATE_ENDED) { |
michael@0 | 664 | return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE; |
michael@0 | 665 | } else { |
michael@0 | 666 | return MediaDecoderOwner::NEXT_FRAME_AVAILABLE; |
michael@0 | 667 | } |
michael@0 | 668 | } |
michael@0 | 669 | |
michael@0 | 670 | void AudioOffloadPlayer::SendMetaDataToHal(sp<AudioSink>& aSink, |
michael@0 | 671 | const sp<MetaData>& aMeta) |
michael@0 | 672 | { |
michael@0 | 673 | int32_t sampleRate = 0; |
michael@0 | 674 | int32_t bitRate = 0; |
michael@0 | 675 | int32_t channelMask = 0; |
michael@0 | 676 | int32_t delaySamples = 0; |
michael@0 | 677 | int32_t paddingSamples = 0; |
michael@0 | 678 | CHECK(aSink.get()); |
michael@0 | 679 | |
michael@0 | 680 | AudioParameter param = AudioParameter(); |
michael@0 | 681 | |
michael@0 | 682 | if (aMeta->findInt32(kKeySampleRate, &sampleRate)) { |
michael@0 | 683 | param.addInt(String8(AUDIO_OFFLOAD_CODEC_SAMPLE_RATE), sampleRate); |
michael@0 | 684 | } |
michael@0 | 685 | if (aMeta->findInt32(kKeyChannelMask, &channelMask)) { |
michael@0 | 686 | param.addInt(String8(AUDIO_OFFLOAD_CODEC_NUM_CHANNEL), channelMask); |
michael@0 | 687 | } |
michael@0 | 688 | if (aMeta->findInt32(kKeyBitRate, &bitRate)) { |
michael@0 | 689 | param.addInt(String8(AUDIO_OFFLOAD_CODEC_AVG_BIT_RATE), bitRate); |
michael@0 | 690 | } |
michael@0 | 691 | if (aMeta->findInt32(kKeyEncoderDelay, &delaySamples)) { |
michael@0 | 692 | param.addInt(String8(AUDIO_OFFLOAD_CODEC_DELAY_SAMPLES), delaySamples); |
michael@0 | 693 | } |
michael@0 | 694 | if (aMeta->findInt32(kKeyEncoderPadding, &paddingSamples)) { |
michael@0 | 695 | param.addInt(String8(AUDIO_OFFLOAD_CODEC_PADDING_SAMPLES), paddingSamples); |
michael@0 | 696 | } |
michael@0 | 697 | |
michael@0 | 698 | AUDIO_OFFLOAD_LOG(PR_LOG_DEBUG, ("SendMetaDataToHal: bitRate %d," |
michael@0 | 699 | " sampleRate %d, chanMask %d, delaySample %d, paddingSample %d", bitRate, |
michael@0 | 700 | sampleRate, channelMask, delaySamples, paddingSamples)); |
michael@0 | 701 | |
michael@0 | 702 | aSink->SetParameters(param.toString()); |
michael@0 | 703 | return; |
michael@0 | 704 | } |
michael@0 | 705 | |
michael@0 | 706 | void AudioOffloadPlayer::SetVolume(double aVolume) |
michael@0 | 707 | { |
michael@0 | 708 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 709 | CHECK(mAudioSink.get()); |
michael@0 | 710 | mAudioSink->SetVolume((float) aVolume); |
michael@0 | 711 | } |
michael@0 | 712 | |
michael@0 | 713 | } // namespace mozilla |