diff options
Diffstat (limited to 'chromium/media/base/android/media_codec_player.cc')
-rw-r--r-- | chromium/media/base/android/media_codec_player.cc | 998 |
1 files changed, 847 insertions, 151 deletions
diff --git a/chromium/media/base/android/media_codec_player.cc b/chromium/media/base/android/media_codec_player.cc index aa05fdf5829..4c297f01b87 100644 --- a/chromium/media/base/android/media_codec_player.cc +++ b/chromium/media/base/android/media_codec_player.cc @@ -12,8 +12,11 @@ #include "base/threading/thread.h" #include "media/base/android/media_codec_audio_decoder.h" #include "media/base/android/media_codec_video_decoder.h" +#include "media/base/android/media_drm_bridge.h" #include "media/base/android/media_player_manager.h" -#include "media/base/buffers.h" +#include "media/base/android/media_task_runner.h" +#include "media/base/bind_to_current_loop.h" +#include "media/base/timestamp_constants.h" #define RUN_ON_MEDIA_THREAD(METHOD, ...) \ do { \ @@ -28,48 +31,41 @@ namespace media { -class MediaThread : public base::Thread { - public: - MediaThread() : base::Thread("BrowserMediaThread") { - Start(); - } -}; - -// Create media thread -base::LazyInstance<MediaThread>::Leaky - g_media_thread = LAZY_INSTANCE_INITIALIZER; - - -scoped_refptr<base::SingleThreadTaskRunner> GetMediaTaskRunner() { - return g_media_thread.Pointer()->task_runner(); -} - // MediaCodecPlayer implementation. MediaCodecPlayer::MediaCodecPlayer( int player_id, base::WeakPtr<MediaPlayerManager> manager, - const RequestMediaResourcesCB& request_media_resources_cb, + const OnDecoderResourcesReleasedCB& on_decoder_resources_released_cb, scoped_ptr<DemuxerAndroid> demuxer, const GURL& frame_url) : MediaPlayerAndroid(player_id, manager.get(), - request_media_resources_cb, + on_decoder_resources_released_cb, frame_url), ui_task_runner_(base::ThreadTaskRunnerHandle::Get()), demuxer_(demuxer.Pass()), - state_(STATE_PAUSED), + state_(kStatePaused), interpolator_(&default_tick_clock_), pending_start_(false), + pending_seek_(kNoTimestamp()), + drm_bridge_(nullptr), + cdm_registration_id_(0), + key_is_required_(false), + key_is_added_(false), media_weak_factory_(this) { DCHECK(ui_task_runner_->BelongsToCurrentThread()); DVLOG(1) << "MediaCodecPlayer::MediaCodecPlayer: player_id:" << player_id; - request_resources_cb_ = base::Bind(request_media_resources_cb_, player_id); - completion_cb_ = base::Bind(&MediaPlayerManager::OnPlaybackComplete, manager, player_id); + waiting_for_decryption_key_cb_ = base::Bind( + &MediaPlayerManager::OnWaitingForDecryptionKey, manager, player_id); + seek_done_cb_ = + base::Bind(&MediaPlayerManager::OnSeekComplete, manager, player_id); + error_cb_ = base::Bind(&MediaPlayerManager::OnError, manager, player_id); + attach_listener_cb_ = base::Bind(&MediaPlayerAndroid::AttachListener, WeakPtrForUIThread(), nullptr); detach_listener_cb_ = @@ -90,6 +86,23 @@ MediaCodecPlayer::~MediaCodecPlayer() { DVLOG(1) << "MediaCodecPlayer::~MediaCodecPlayer"; DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + + // Currently the unit tests wait for the MediaCodecPlayer destruction by + // watching the demuxer, which is destroyed as one of the member variables. + // Release the codecs here, before any member variable is destroyed to make + // the unit tests happy. + + if (video_decoder_) + video_decoder_->ReleaseDecoderResources(); + if (audio_decoder_) + audio_decoder_->ReleaseDecoderResources(); + + media_stat_->StopAndReport(GetInterpolatedTime()); + + if (drm_bridge_) { + DCHECK(cdm_registration_id_); + drm_bridge_->UnregisterPlayer(cdm_registration_id_); + } } void MediaCodecPlayer::Initialize() { @@ -127,16 +140,70 @@ void MediaCodecPlayer::SetVideoSurface(gfx::ScopedJavaSurface surface) { DVLOG(1) << __FUNCTION__ << (surface.IsEmpty() ? " empty" : " non-empty"); - // I assume that if video decoder already has the surface, - // there will be two calls: - // (1) SetVideoSurface(0) - // (2) SetVideoSurface(new_surface) - video_decoder_->SetPendingSurface(surface.Pass()); + // Save the empty-ness before we pass the surface to the decoder. + bool surface_is_empty = surface.IsEmpty(); + + // Apparently RemoveVideoSurface() can be called several times in a row, + // ignore the second and subsequent calls. + if (surface_is_empty && !video_decoder_->HasVideoSurface()) { + DVLOG(1) << __FUNCTION__ << ": surface already removed, ignoring"; + return; + } + + // Do not set unprotected surface if we know that we need a protected one. + // Empty surface means the surface removal and we always allow for it. + if (!surface_is_empty && video_decoder_->IsProtectedSurfaceRequired() && + !surface.is_protected()) { + DVLOG(0) << __FUNCTION__ << ": surface is not protected, ignoring"; + return; + } - if (video_decoder_->HasPendingSurface() && - state_ == STATE_WAITING_FOR_SURFACE) { - SetState(STATE_PLAYING); - StartPlaybackDecoders(); + video_decoder_->SetVideoSurface(surface.Pass()); + + if (surface_is_empty) { + // Remove video surface. + switch (state_) { + case kStatePlaying: + if (VideoFinished()) + break; + + DVLOG(1) << __FUNCTION__ << ": stopping and restarting"; + // Stop decoders as quickly as possible. + StopDecoders(); // synchronous stop + + // Prefetch or wait for initial configuration. + if (HasAudio() || HasVideo()) { + SetState(kStatePrefetching); + StartPrefetchDecoders(); + } else { + SetState(kStateWaitingForConfig); + } + break; + + default: + break; // ignore + } + } else { + // Replace video surface. + switch (state_) { + case kStateWaitingForSurface: + SetState(kStatePlaying); + StartPlaybackOrBrowserSeek(); + break; + + case kStatePlaying: + if (VideoFinished()) + break; + + DVLOG(1) << __FUNCTION__ << ": requesting to stop and restart"; + SetState(kStateStopping); + RequestToStopDecoders(); + SetPendingStart(true); + break; + + default: + break; // ignore + } } } @@ -146,19 +213,30 @@ void MediaCodecPlayer::Start() { DVLOG(1) << __FUNCTION__; switch (state_) { - case STATE_PAUSED: + case kStatePaused: + // Request play permission or wait for initial configuration. if (HasAudio() || HasVideo()) { - SetState(STATE_PREFETCHING); - StartPrefetchDecoders(); + SetState(kStateWaitingForPermission); + RequestPlayPermission(); } else { - SetState(STATE_WAITING_FOR_CONFIG); + SetState(kStateWaitingForConfig); } break; - case STATE_STOPPING: + case kStateStopping: + case kStateWaitingForSeek: SetPendingStart(true); break; + case kStateWaitingForConfig: + case kStateWaitingForPermission: + case kStatePrefetching: + case kStatePlaying: + case kStateWaitingForSurface: + case kStateWaitingForKey: + case kStateWaitingForMediaCrypto: + case kStateError: + break; // Ignore default: - // Ignore + NOTREACHED(); break; } } @@ -168,21 +246,29 @@ void MediaCodecPlayer::Pause(bool is_media_related_action) { DVLOG(1) << __FUNCTION__; + SetPendingStart(false); + switch (state_) { - case STATE_PREFETCHING: - SetState(STATE_PAUSED); - StopDecoders(); - break; - case STATE_WAITING_FOR_SURFACE: - SetState(STATE_PAUSED); + case kStateWaitingForConfig: + case kStateWaitingForPermission: + case kStatePrefetching: + case kStateWaitingForSurface: + case kStateWaitingForKey: + case kStateWaitingForMediaCrypto: + SetState(kStatePaused); StopDecoders(); break; - case STATE_PLAYING: - SetState(STATE_STOPPING); + case kStatePlaying: + SetState(kStateStopping); RequestToStopDecoders(); break; + case kStatePaused: + case kStateStopping: + case kStateWaitingForSeek: + case kStateError: + break; // Ignore default: - // Ignore + NOTREACHED(); break; } } @@ -191,16 +277,77 @@ void MediaCodecPlayer::SeekTo(base::TimeDelta timestamp) { RUN_ON_MEDIA_THREAD(SeekTo, timestamp); DVLOG(1) << __FUNCTION__ << " " << timestamp; - NOTIMPLEMENTED(); + + switch (state_) { + case kStatePaused: + SetState(kStateWaitingForSeek); + RequestDemuxerSeek(timestamp); + break; + case kStateWaitingForConfig: + case kStateWaitingForPermission: + case kStatePrefetching: + case kStateWaitingForSurface: + case kStateWaitingForKey: + case kStateWaitingForMediaCrypto: + SetState(kStateWaitingForSeek); + StopDecoders(); + SetPendingStart(true); + RequestDemuxerSeek(timestamp); + break; + case kStatePlaying: + SetState(kStateStopping); + RequestToStopDecoders(); + SetPendingStart(true); + SetPendingSeek(timestamp); + break; + case kStateStopping: + SetPendingSeek(timestamp); + break; + case kStateWaitingForSeek: + SetPendingSeek(timestamp); + break; + case kStateError: + break; // ignore + default: + NOTREACHED(); + break; + } } void MediaCodecPlayer::Release() { + // TODO(qinmin): the callback should be posted onto the UI thread when + // Release() finishes on media thread. However, the BrowserMediaPlayerManager + // could be gone in that case, which cause the MediaThrottler unable to + // track the active players. We should pass + // MediaThrottler::OnDecodeRequestFinished() to this class in the ctor, but + // also need a way for BrowserMediaPlayerManager to track active players. + if (ui_task_runner_->BelongsToCurrentThread()) + on_decoder_resources_released_cb_.Run(player_id()); + RUN_ON_MEDIA_THREAD(Release); DVLOG(1) << __FUNCTION__; - SetState(STATE_PAUSED); + // Stop decoding threads and delete MediaCodecs, but keep IPC between browser + // and renderer processes going. Seek should work across and after Release(). + ReleaseDecoderResources(); + + SetPendingStart(false); + + if (state_ != kStateWaitingForSeek) + SetState(kStatePaused); + + // Crear encryption key related flags. + key_is_required_ = false; + key_is_added_ = false; + + base::TimeDelta pending_seek_time = GetPendingSeek(); + if (pending_seek_time != kNoTimestamp()) { + SetPendingSeek(kNoTimestamp()); + SetState(kStateWaitingForSeek); + RequestDemuxerSeek(pending_seek_time); + } } void MediaCodecPlayer::SetVolume(double volume) { @@ -232,7 +379,10 @@ base::TimeDelta MediaCodecPlayer::GetDuration() { bool MediaCodecPlayer::IsPlaying() { DCHECK(ui_task_runner_->BelongsToCurrentThread()); - return state_ == STATE_PLAYING; + + // TODO(timav): Use another variable since |state_| should only be accessed on + // Media thread. + return state_ == kStatePlaying || state_ == kStateStopping; } bool MediaCodecPlayer::CanPause() { @@ -261,8 +411,39 @@ bool MediaCodecPlayer::IsPlayerReady() { } void MediaCodecPlayer::SetCdm(BrowserCdm* cdm) { - DCHECK(ui_task_runner_->BelongsToCurrentThread()); - NOTIMPLEMENTED(); + RUN_ON_MEDIA_THREAD(SetCdm, cdm); + + DVLOG(1) << __FUNCTION__; + + // Currently we don't support DRM change during the middle of playback, even + // if the player is paused. There is no current plan to support it, see + // http://crbug.com/253792. + if (state_ != kStatePaused || GetInterpolatedTime() > base::TimeDelta()) { + VLOG(0) << "Setting DRM bridge after playback has started is not supported"; + return; + } + + if (drm_bridge_) { + NOTREACHED() << "Currently we do not support resetting CDM."; + return; + } + + DCHECK(cdm); + drm_bridge_ = static_cast<MediaDrmBridge*>(cdm); + + DCHECK(drm_bridge_); + + cdm_registration_id_ = drm_bridge_->RegisterPlayer( + base::Bind(&MediaCodecPlayer::OnKeyAdded, media_weak_this_), + base::Bind(&MediaCodecPlayer::OnCdmUnset, media_weak_this_)); + + MediaDrmBridge::MediaCryptoReadyCB cb = BindToCurrentLoop( + base::Bind(&MediaCodecPlayer::OnMediaCryptoReady, media_weak_this_)); + + // Post back to UI thread. + ui_task_runner_->PostTask(FROM_HERE, + base::Bind(&MediaDrmBridge::SetMediaCryptoReadyCB, + drm_bridge_->WeakPtrForUIThread(), cb)); } // Callbacks from Demuxer. @@ -304,7 +485,67 @@ void MediaCodecPlayer::OnDemuxerSeekDone( DVLOG(1) << __FUNCTION__ << " actual_time:" << actual_browser_seek_time; - NOTIMPLEMENTED(); + DCHECK(seek_info_.get()); + DCHECK(seek_info_->seek_time != kNoTimestamp()); + + // A browser seek must not jump into the past. Ideally, it seeks to the + // requested time, but it might jump into the future. + DCHECK(!seek_info_->is_browser_seek || + seek_info_->seek_time <= actual_browser_seek_time); + + // Restrict the current time to be equal to seek_time + // for the next StartPlaybackDecoders() call. + + base::TimeDelta seek_time = seek_info_->is_browser_seek + ? actual_browser_seek_time + : seek_info_->seek_time; + + interpolator_.SetBounds(seek_time, seek_time); + + audio_decoder_->SetBaseTimestamp(seek_time); + + audio_decoder_->SetPrerollTimestamp(seek_time); + video_decoder_->SetPrerollTimestamp(seek_time); + + // The Flush() might set the state to kStateError. + if (state_ == kStateError) { + // Notify the Renderer. + if (!seek_info_->is_browser_seek) + ui_task_runner_->PostTask(FROM_HERE, + base::Bind(seek_done_cb_, seek_time)); + + seek_info_.reset(); + return; + } + + DCHECK_EQ(kStateWaitingForSeek, state_); + + base::TimeDelta pending_seek_time = GetPendingSeek(); + if (pending_seek_time != kNoTimestamp()) { + // Keep kStateWaitingForSeek + SetPendingSeek(kNoTimestamp()); + RequestDemuxerSeek(pending_seek_time); + return; + } + + if (HasPendingStart()) { + SetPendingStart(false); + // Request play permission or wait for initial configuration. + if (HasAudio() || HasVideo()) { + SetState(kStateWaitingForPermission); + RequestPlayPermission(); + } else { + SetState(kStateWaitingForConfig); + } + } else { + SetState(kStatePaused); + } + + // Notify the Renderer. + if (!seek_info_->is_browser_seek) + ui_task_runner_->PostTask(FROM_HERE, base::Bind(seek_done_cb_, seek_time)); + + seek_info_.reset(); } void MediaCodecPlayer::OnDemuxerDurationChanged( @@ -315,8 +556,58 @@ void MediaCodecPlayer::OnDemuxerDurationChanged( duration_ = duration; } +void MediaCodecPlayer::SetDecodersTimeCallbackForTests( + DecodersTimeCallback cb) { + DCHECK(ui_task_runner_->BelongsToCurrentThread()); + decoders_time_cb_ = cb; +} + +void MediaCodecPlayer::SetCodecCreatedCallbackForTests( + CodecCreatedCallback cb) { + DCHECK(ui_task_runner_->BelongsToCurrentThread()); + DCHECK(audio_decoder_ && video_decoder_); + + audio_decoder_->SetCodecCreatedCallbackForTests( + base::Bind(cb, DemuxerStream::AUDIO)); + video_decoder_->SetCodecCreatedCallbackForTests( + base::Bind(cb, DemuxerStream::VIDEO)); +} + +void MediaCodecPlayer::SetAlwaysReconfigureForTests(DemuxerStream::Type type) { + DCHECK(ui_task_runner_->BelongsToCurrentThread()); + DCHECK(audio_decoder_ && video_decoder_); + + if (type == DemuxerStream::AUDIO) + audio_decoder_->SetAlwaysReconfigureForTests(); + else if (type == DemuxerStream::VIDEO) + video_decoder_->SetAlwaysReconfigureForTests(); +} + +bool MediaCodecPlayer::IsPrerollingForTests(DemuxerStream::Type type) const { + DCHECK(ui_task_runner_->BelongsToCurrentThread()); + DCHECK(audio_decoder_ && video_decoder_); + + if (type == DemuxerStream::AUDIO) + return audio_decoder_->IsPrerollingForTests(); + else if (type == DemuxerStream::VIDEO) + return video_decoder_->IsPrerollingForTests(); + else + return false; +} + // Events from Player, called on UI thread +void MediaCodecPlayer::RequestPermissionAndPostResult( + base::TimeDelta duration) { + DCHECK(ui_task_runner_->BelongsToCurrentThread()); + DVLOG(1) << __FUNCTION__ << " duration:" << duration; + + bool granted = manager()->RequestPlay(player_id(), duration); + GetMediaTaskRunner()->PostTask( + FROM_HERE, base::Bind(&MediaCodecPlayer::OnPermissionDecided, + media_weak_this_, granted)); +} + void MediaCodecPlayer::OnMediaMetadataChanged(base::TimeDelta duration, const gfx::Size& video_size) { DCHECK(ui_task_runner_->BelongsToCurrentThread()); @@ -340,6 +631,35 @@ void MediaCodecPlayer::OnTimeUpdate(base::TimeDelta current_timestamp, manager()->OnTimeUpdate(player_id(), current_timestamp, current_time_ticks); } +// Event from manager, called on Media thread + +void MediaCodecPlayer::OnPermissionDecided(bool granted) { + DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + + DVLOG(1) << __FUNCTION__ << ": " << (granted ? "granted" : "denied"); + + switch (state_) { + case kStateWaitingForPermission: + if (granted) { + SetState(kStatePrefetching); + StartPrefetchDecoders(); + } else { + SetState(kStatePaused); + StopDecoders(); + } + break; + + case kStatePaused: + case kStateWaitingForSeek: + case kStateError: + break; // ignore + + default: + NOTREACHED() << __FUNCTION__ << ": wrong state " << AsString(state_); + break; + } +} + // Events from Decoders, called on Media thread void MediaCodecPlayer::RequestDemuxerData(DemuxerStream::Type stream_type) { @@ -361,59 +681,143 @@ void MediaCodecPlayer::RequestDemuxerData(DemuxerStream::Type stream_type) { void MediaCodecPlayer::OnPrefetchDone() { DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); - DVLOG(1) << __FUNCTION__; - if (state_ != STATE_PREFETCHING) + if (state_ != kStatePrefetching) { + DVLOG(1) << __FUNCTION__ << " wrong state " << AsString(state_) + << " ignoring"; return; // Ignore + } + + DVLOG(1) << __FUNCTION__; if (!HasAudio() && !HasVideo()) { // No configuration at all after prefetching. // This is an error, initial configuration is expected // before the first data chunk. - GetMediaTaskRunner()->PostTask(FROM_HERE, error_cb_); + DCHECK(!internal_error_cb_.is_null()); + GetMediaTaskRunner()->PostTask(FROM_HERE, internal_error_cb_); + return; + } + + if (HasVideo() && !video_decoder_->HasVideoSurface()) { + SetState(kStateWaitingForSurface); return; } - if (HasVideo() && !HasPendingSurface()) { - SetState(STATE_WAITING_FOR_SURFACE); + if (key_is_required_ && !key_is_added_) { + SetState(kStateWaitingForKey); + ui_task_runner_->PostTask(FROM_HERE, waiting_for_decryption_key_cb_); return; } - SetState(STATE_PLAYING); - StartPlaybackDecoders(); + SetState(kStatePlaying); + StartPlaybackOrBrowserSeek(); } -void MediaCodecPlayer::OnStopDone() { +void MediaCodecPlayer::OnPrerollDone() { DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + + if (state_ != kStatePlaying) { + DVLOG(1) << __FUNCTION__ << ": in state " << AsString(state_) + << ", ignoring"; + return; + } + DVLOG(1) << __FUNCTION__; - if (!(audio_decoder_->IsStopped() && video_decoder_->IsStopped())) + StartStatus status = StartDecoders(); + if (status != kStartOk) + GetMediaTaskRunner()->PostTask(FROM_HERE, internal_error_cb_); +} + +void MediaCodecPlayer::OnDecoderDrained(DemuxerStream::Type type) { + DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + DVLOG(1) << __FUNCTION__ << " " << type; + + // We expect that OnStopDone() comes next. + + DCHECK(type == DemuxerStream::AUDIO || type == DemuxerStream::VIDEO); + + // DCHECK(state_ == kStatePlaying || state_ == kStateStopping) + // << __FUNCTION__ << " illegal state: " << AsString(state_); + // + // With simultaneous reconfiguration of audio and video streams the state + // can be kStatePrefetching as well: + // OnLastFrameRendered VIDEO (VIDEO decoder is stopped) + // OnLastFrameRendered AUDIO (AUDIO decoder is stopped) + // OnDecoderDrained VIDEO (kStatePlaying -> kStateStopping) + // OnStopDone VIDEO (kStateStopping -> kStatePrefetching) + // OnDecoderDrained AUDIO + // OnStopDone AUDIO + // + // TODO(timav): combine OnDecoderDrained() and OnStopDone() ? + + switch (state_) { + case kStatePlaying: + SetState(kStateStopping); + SetPendingStart(true); + + if (type == DemuxerStream::AUDIO && !VideoFinished()) { + DVLOG(1) << __FUNCTION__ << " requesting to stop video"; + video_decoder_->RequestToStop(); + } else if (type == DemuxerStream::VIDEO && !AudioFinished()) { + DVLOG(1) << __FUNCTION__ << " requesting to stop audio"; + audio_decoder_->RequestToStop(); + } + break; + + default: + break; + } +} + +void MediaCodecPlayer::OnStopDone(DemuxerStream::Type type) { + DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + DVLOG(1) << __FUNCTION__ << " " << type + << " interpolated time:" << GetInterpolatedTime(); + + if (!(audio_decoder_->IsStopped() && video_decoder_->IsStopped())) { + DVLOG(1) << __FUNCTION__ << " both audio and video has to be stopped" + << ", ignoring"; return; // Wait until other stream is stopped + } // At this point decoder threads should not be running if (interpolator_.interpolating()) interpolator_.StopInterpolating(); + base::TimeDelta seek_time; switch (state_) { - case STATE_STOPPING: - if (HasPendingStart()) { + case kStateStopping: { + base::TimeDelta seek_time = GetPendingSeek(); + if (seek_time != kNoTimestamp()) { + SetState(kStateWaitingForSeek); + SetPendingSeek(kNoTimestamp()); + RequestDemuxerSeek(seek_time); + } else if (HasPendingStart()) { SetPendingStart(false); - SetState(STATE_PREFETCHING); - StartPrefetchDecoders(); + SetState(kStateWaitingForPermission); + RequestPlayPermission(); } else { - SetState(STATE_PAUSED); + SetState(kStatePaused); } - break; - case STATE_PLAYING: + } break; + case kStatePlaying: // Unexpected stop means completion - SetState(STATE_PAUSED); + SetState(kStatePaused); break; default: - DVLOG(0) << __FUNCTION__ << " illegal state: " << AsString(state_); - NOTREACHED(); - break; + // DVLOG(0) << __FUNCTION__ << " illegal state: " << AsString(state_); + // NOTREACHED(); + // Ignore! There can be a race condition: audio posts OnStopDone, + // then video posts, then first OnStopDone arrives at which point + // both streams are already stopped, then second OnStopDone arrives. When + // the second one arrives, the state us not kStateStopping any more. + return; } + media_stat_->StopAndReport(GetInterpolatedTime()); + // DetachListener to UI thread ui_task_runner_->PostTask(FROM_HERE, detach_listener_cb_); @@ -421,46 +825,86 @@ void MediaCodecPlayer::OnStopDone() { ui_task_runner_->PostTask(FROM_HERE, completion_cb_); } +void MediaCodecPlayer::OnMissingKeyReported(DemuxerStream::Type type) { + DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + DVLOG(1) << __FUNCTION__ << " " << type; + + // Request stop and restart to pick up the key. + key_is_required_ = true; + + if (state_ == kStatePlaying) { + SetState(kStateStopping); + RequestToStopDecoders(); + SetPendingStart(true); + } +} + void MediaCodecPlayer::OnError() { DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); DVLOG(1) << __FUNCTION__; - // STATE_ERROR blocks all events - SetState(STATE_ERROR); + // kStateError blocks all events + SetState(kStateError); ReleaseDecoderResources(); + + ui_task_runner_->PostTask(FROM_HERE, + base::Bind(error_cb_, MEDIA_ERROR_DECODE)); } void MediaCodecPlayer::OnStarvation(DemuxerStream::Type type) { DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); DVLOG(1) << __FUNCTION__ << " stream type:" << type; - if (state_ != STATE_PLAYING) + if (state_ != kStatePlaying) return; // Ignore - SetState(STATE_STOPPING); + SetState(kStateStopping); RequestToStopDecoders(); SetPendingStart(true); + + media_stat_->AddStarvation(); } void MediaCodecPlayer::OnTimeIntervalUpdate(DemuxerStream::Type type, base::TimeDelta now_playing, - base::TimeDelta last_buffered) { + base::TimeDelta last_buffered, + bool postpone) { DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); - interpolator_.SetBounds(now_playing, last_buffered); + DVLOG(2) << __FUNCTION__ << ": stream type:" << type << " [" << now_playing + << "," << last_buffered << "]" << (postpone ? " postpone" : ""); + + // For testing only: report time interval as we receive it from decoders + // as an indication of what is being rendered. Do not post this callback + // for postponed frames: although the PTS is correct, the tests also care + // about the wall clock time this callback arrives and deduce the rendering + // moment from it. + if (!decoders_time_cb_.is_null() && !postpone) { + ui_task_runner_->PostTask( + FROM_HERE, + base::Bind(decoders_time_cb_, type, now_playing, last_buffered)); + } - // Post to UI thread - ui_task_runner_->PostTask(FROM_HERE, - base::Bind(time_update_cb_, GetInterpolatedTime(), - base::TimeTicks::Now())); -} + // I assume that audio stream cannot be added after we get configs by + // OnDemuxerConfigsAvailable(), but that audio can finish early. -void MediaCodecPlayer::OnVideoCodecCreated() { - DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + if (type == DemuxerStream::VIDEO) { + // Ignore video PTS if there is audio stream or if it's behind current + // time as set by audio stream. + if (!AudioFinished() || + (HasAudio() && now_playing < interpolator_.GetInterpolatedTime())) + return; + } - // This callback requests resources by releasing other players. - ui_task_runner_->PostTask(FROM_HERE, request_resources_cb_); + interpolator_.SetBounds(now_playing, last_buffered); + + // Post to UI thread + if (!postpone) { + ui_task_runner_->PostTask(FROM_HERE, + base::Bind(time_update_cb_, GetInterpolatedTime(), + base::TimeTicks::Now())); + } } void MediaCodecPlayer::OnVideoResolutionChanged(const gfx::Size& size) { @@ -473,24 +917,86 @@ void MediaCodecPlayer::OnVideoResolutionChanged(const gfx::Size& size) { FROM_HERE, base::Bind(metadata_changed_cb_, kNoTimestamp(), size)); } -// State machine operations, called on Media thread +// Callbacks from MediaDrmBridge. -void MediaCodecPlayer::SetState(PlayerState new_state) { +void MediaCodecPlayer::OnMediaCryptoReady( + MediaDrmBridge::JavaObjectPtr media_crypto, + bool needs_protected_surface) { DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + DVLOG(1) << __FUNCTION__ << " protected surface is " + << (needs_protected_surface ? "required" : "not required"); - DVLOG(1) << "SetState:" << AsString(state_) << " -> " << AsString(new_state); - state_ = new_state; + // We use the parameters that come with this callback every time we call + // Configure(). This is possible only if the MediaCrypto object remains valid + // and the surface requirement does not change until new SetCdm() is called. + + DCHECK(media_crypto); + DCHECK(!media_crypto->is_null()); + + media_crypto_ = media_crypto.Pass(); + + if (audio_decoder_) { + audio_decoder_->SetNeedsReconfigure(); + } + + if (video_decoder_) { + video_decoder_->SetNeedsReconfigure(); + video_decoder_->SetProtectedSurfaceRequired(needs_protected_surface); + } + + if (state_ == kStateWaitingForMediaCrypto) { + // Resume start sequence (configure, etc.) + SetState(kStatePlaying); + StartPlaybackOrBrowserSeek(); + } + + DVLOG(1) << __FUNCTION__ << " end"; } -void MediaCodecPlayer::SetPendingSurface(gfx::ScopedJavaSurface surface) { +void MediaCodecPlayer::OnKeyAdded() { DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); DVLOG(1) << __FUNCTION__; - video_decoder_->SetPendingSurface(surface.Pass()); + key_is_added_ = true; + + if (state_ == kStateWaitingForKey) { + SetState(kStatePlaying); + StartPlaybackOrBrowserSeek(); + } +} + +void MediaCodecPlayer::OnCdmUnset() { + DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + DVLOG(1) << __FUNCTION__; + + // This comment is copied from MediaSourcePlayer::OnCdmUnset(). + // TODO(xhwang): Currently this is only called during teardown. Support full + // detachment of CDM during playback. This will be needed when we start to + // support setMediaKeys(0) (see http://crbug.com/330324), or when we release + // MediaDrm when the video is paused, or when the device goes to sleep (see + // http://crbug.com/272421). + + if (audio_decoder_) { + audio_decoder_->SetNeedsReconfigure(); + } + + if (video_decoder_) { + video_decoder_->SetProtectedSurfaceRequired(false); + video_decoder_->SetNeedsReconfigure(); + } + + cdm_registration_id_ = 0; + drm_bridge_ = nullptr; + media_crypto_.reset(); } -bool MediaCodecPlayer::HasPendingSurface() { - return video_decoder_->HasPendingSurface(); +// State machine operations, called on Media thread + +void MediaCodecPlayer::SetState(PlayerState new_state) { + DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + + DVLOG(1) << "SetState:" << AsString(state_) << " -> " << AsString(new_state); + state_ = new_state; } void MediaCodecPlayer::SetPendingStart(bool need_to_start) { @@ -499,17 +1005,28 @@ void MediaCodecPlayer::SetPendingStart(bool need_to_start) { pending_start_ = need_to_start; } -bool MediaCodecPlayer::HasPendingStart() { +bool MediaCodecPlayer::HasPendingStart() const { DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); return pending_start_; } -bool MediaCodecPlayer::HasAudio() { +void MediaCodecPlayer::SetPendingSeek(base::TimeDelta timestamp) { + DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + DVLOG(1) << __FUNCTION__ << ": " << timestamp; + pending_seek_ = timestamp; +} + +base::TimeDelta MediaCodecPlayer::GetPendingSeek() const { + DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + return pending_seek_; +} + +bool MediaCodecPlayer::HasAudio() const { DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); return audio_decoder_->HasStream(); } -bool MediaCodecPlayer::HasVideo() { +bool MediaCodecPlayer::HasVideo() const { DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); return video_decoder_->HasStream(); } @@ -531,12 +1048,24 @@ void MediaCodecPlayer::SetDemuxerConfigs(const DemuxerConfigs& configs) { if (configs.video_codec != kUnknownVideoCodec) video_decoder_->SetDemuxerConfigs(configs); - if (state_ == STATE_WAITING_FOR_CONFIG) { - SetState(STATE_PREFETCHING); - StartPrefetchDecoders(); + if (state_ == kStateWaitingForConfig) { + SetState(kStateWaitingForPermission); + RequestPlayPermission(); } } +void MediaCodecPlayer::RequestPlayPermission() { + DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + DVLOG(1) << __FUNCTION__; + + // Check that we have received demuxer config hence we know the duration. + DCHECK(HasAudio() || HasVideo()); + + ui_task_runner_->PostTask( + FROM_HERE, base::Bind(&MediaPlayerAndroid::RequestPermissionAndPostResult, + WeakPtrForUIThread(), duration_)); +} + void MediaCodecPlayer::StartPrefetchDecoders() { DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); DVLOG(1) << __FUNCTION__; @@ -565,66 +1094,192 @@ void MediaCodecPlayer::StartPrefetchDecoders() { video_decoder_->Prefetch(prefetch_cb); } -void MediaCodecPlayer::StartPlaybackDecoders() { +void MediaCodecPlayer::StartPlaybackOrBrowserSeek() { + DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + DVLOG(1) << __FUNCTION__; + + // TODO(timav): consider replacing this method with posting a + // browser seek task (i.e. generate an event) from StartPlaybackDecoders(). + + // Clear encryption key related flags. + key_is_required_ = false; + key_is_added_ = false; + + StartStatus status = StartPlaybackDecoders(); + + switch (status) { + case kStartBrowserSeekRequired: + // Browser seek + SetState(kStateWaitingForSeek); + SetPendingStart(true); + StopDecoders(); + RequestDemuxerSeek(GetInterpolatedTime(), true); + break; + case kStartCryptoRequired: + SetState(kStateWaitingForMediaCrypto); + break; + case kStartFailed: + GetMediaTaskRunner()->PostTask(FROM_HERE, internal_error_cb_); + break; + case kStartOk: + break; + } +} + +MediaCodecPlayer::StartStatus MediaCodecPlayer::StartPlaybackDecoders() { DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); DVLOG(1) << __FUNCTION__; - // Configure all streams before the start since - // we may discover that browser seek is required. + // Configure all streams before the start since we may discover that browser + // seek is required. + MediaCodecPlayer::StartStatus status = ConfigureDecoders(); + if (status != kStartOk) + return status; + + bool preroll_required = false; + status = MaybePrerollDecoders(&preroll_required); + if (preroll_required) + return status; + + return StartDecoders(); +} + +MediaCodecPlayer::StartStatus MediaCodecPlayer::ConfigureDecoders() { + DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + DVLOG(1) << __FUNCTION__; - bool do_audio = !AudioFinished(); - bool do_video = !VideoFinished(); + const bool do_audio = !AudioFinished(); + const bool do_video = !VideoFinished(); - // If there is nothing to play, the state machine should determine - // this at the prefetch state and never call this method. + // If there is nothing to play, the state machine should determine this at the + // prefetch state and never call this method. DCHECK(do_audio || do_video); - if (do_audio) { - MediaCodecDecoder::ConfigStatus status = audio_decoder_->Configure(); - if (status != MediaCodecDecoder::CONFIG_OK) { - GetMediaTaskRunner()->PostTask(FROM_HERE, error_cb_); - return; + const bool need_audio_crypto = + do_audio && audio_decoder_->IsContentEncrypted(); + const bool need_video_crypto = + do_video && video_decoder_->IsContentEncrypted(); + + // Do we need to create a local ref from the global ref? + jobject media_crypto = media_crypto_ ? media_crypto_->obj() : nullptr; + + if (need_audio_crypto || need_video_crypto) { + DVLOG(1) << (need_audio_crypto ? " audio" : "") + << (need_video_crypto ? " video" : "") << " need(s) encryption"; + if (!media_crypto) { + DVLOG(1) << __FUNCTION__ << ": MediaCrypto is not found, returning"; + return kStartCryptoRequired; } } - if (do_video) { - MediaCodecDecoder::ConfigStatus status = video_decoder_->Configure(); - if (status != MediaCodecDecoder::CONFIG_OK) { - GetMediaTaskRunner()->PostTask(FROM_HERE, error_cb_); - return; - } + // Start with video: if browser seek is required it would not make sense to + // configure audio. + + MediaCodecDecoder::ConfigStatus status = MediaCodecDecoder::kConfigOk; + if (do_video) + status = video_decoder_->Configure(media_crypto); + + if (status == MediaCodecDecoder::kConfigOk && do_audio) + status = audio_decoder_->Configure(media_crypto); + + switch (status) { + case MediaCodecDecoder::kConfigOk: + break; + case MediaCodecDecoder::kConfigKeyFrameRequired: + return kStartBrowserSeekRequired; + case MediaCodecDecoder::kConfigFailure: + return kStartFailed; + } + return kStartOk; +} + +MediaCodecPlayer::StartStatus MediaCodecPlayer::MaybePrerollDecoders( + bool* preroll_required) { + DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + + DVLOG(1) << __FUNCTION__ << " current_time:" << GetInterpolatedTime(); + + // If requested, preroll is always done in the beginning of the playback, + // after prefetch. The request might not happen at all though, in which case + // we won't have prerolling phase. We need the prerolling when we (re)create + // the decoder, because its configuration and initialization (getting input, + // but not making output) can take time, and after the seek because there + // could be some data to be skipped and there is again initialization after + // the flush. + + *preroll_required = false; + + int count = 0; + const bool do_audio = audio_decoder_->NotCompletedAndNeedsPreroll(); + if (do_audio) + ++count; + + const bool do_video = video_decoder_->NotCompletedAndNeedsPreroll(); + if (do_video) + ++count; + + if (count == 0) { + DVLOG(1) << __FUNCTION__ << ": preroll is not required, skipping"; + return kStartOk; } - // At this point decoder threads should not be running. + *preroll_required = true; + + DCHECK(count > 0); + DCHECK(do_audio || do_video); + + DVLOG(1) << __FUNCTION__ << ": preroll for " << count << " stream(s)"; + + base::Closure preroll_cb = base::BarrierClosure( + count, base::Bind(&MediaCodecPlayer::OnPrerollDone, media_weak_this_)); + + if (do_audio && !audio_decoder_->Preroll(preroll_cb)) + return kStartFailed; + + if (do_video && !video_decoder_->Preroll(preroll_cb)) + return kStartFailed; + + return kStartOk; +} + +MediaCodecPlayer::StartStatus MediaCodecPlayer::StartDecoders() { + DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + if (!interpolator_.interpolating()) interpolator_.StartInterpolating(); base::TimeDelta current_time = GetInterpolatedTime(); - if (do_audio) { - if (!audio_decoder_->Start(current_time)) { - GetMediaTaskRunner()->PostTask(FROM_HERE, error_cb_); - return; - } + DVLOG(1) << __FUNCTION__ << " current_time:" << current_time; + + // At this point decoder threads are either not running at all or their + // message pumps are in the idle state after the preroll is done. + media_stat_->Start(current_time); + + if (!AudioFinished()) { + if (!audio_decoder_->Start(current_time)) + return kStartFailed; // Attach listener on UI thread ui_task_runner_->PostTask(FROM_HERE, attach_listener_cb_); } - if (do_video) { - if (!video_decoder_->Start(current_time)) { - GetMediaTaskRunner()->PostTask(FROM_HERE, error_cb_); - return; - } + if (!VideoFinished()) { + if (!video_decoder_->Start(current_time)) + return kStartFailed; } + + return kStartOk; } void MediaCodecPlayer::StopDecoders() { DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); DVLOG(1) << __FUNCTION__; - audio_decoder_->SyncStop(); video_decoder_->SyncStop(); + audio_decoder_->SyncStop(); + + media_stat_->StopAndReport(GetInterpolatedTime()); } void MediaCodecPlayer::RequestToStopDecoders() { @@ -641,7 +1296,8 @@ void MediaCodecPlayer::RequestToStopDecoders() { if (!do_audio && !do_video) { GetMediaTaskRunner()->PostTask( - FROM_HERE, base::Bind(&MediaCodecPlayer::OnStopDone, media_weak_this_)); + FROM_HERE, base::Bind(&MediaCodecPlayer::OnStopDone, media_weak_this_, + DemuxerStream::UNKNOWN)); return; } @@ -651,6 +1307,23 @@ void MediaCodecPlayer::RequestToStopDecoders() { video_decoder_->RequestToStop(); } +void MediaCodecPlayer::RequestDemuxerSeek(base::TimeDelta seek_time, + bool is_browser_seek) { + DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); + DVLOG(1) << __FUNCTION__ << " " << seek_time + << (is_browser_seek ? " BROWSER_SEEK" : ""); + + // Flush decoders before requesting demuxer. + audio_decoder_->Flush(); + video_decoder_->Flush(); + + // Save active seek data. Logically it is attached to kStateWaitingForSeek. + DCHECK_EQ(kStateWaitingForSeek, state_); + seek_info_.reset(new SeekInfo(seek_time, is_browser_seek)); + + demuxer_->RequestDemuxerSeek(seek_time, is_browser_seek); +} + void MediaCodecPlayer::ReleaseDecoderResources() { DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); DVLOG(1) << __FUNCTION__; @@ -664,39 +1337,58 @@ void MediaCodecPlayer::ReleaseDecoderResources() { // At this point decoder threads should not be running if (interpolator_.interpolating()) interpolator_.StopInterpolating(); + + media_stat_->StopAndReport(GetInterpolatedTime()); } void MediaCodecPlayer::CreateDecoders() { DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); DVLOG(1) << __FUNCTION__; - error_cb_ = base::Bind(&MediaCodecPlayer::OnError, media_weak_this_); + internal_error_cb_ = base::Bind(&MediaCodecPlayer::OnError, media_weak_this_); + + media_stat_.reset(new MediaStatistics()); audio_decoder_.reset(new MediaCodecAudioDecoder( - GetMediaTaskRunner(), base::Bind(&MediaCodecPlayer::RequestDemuxerData, - media_weak_this_, DemuxerStream::AUDIO), + GetMediaTaskRunner(), &media_stat_->audio_frame_stats(), + base::Bind(&MediaCodecPlayer::RequestDemuxerData, media_weak_this_, + DemuxerStream::AUDIO), base::Bind(&MediaCodecPlayer::OnStarvation, media_weak_this_, DemuxerStream::AUDIO), - base::Bind(&MediaCodecPlayer::OnStopDone, media_weak_this_), error_cb_, + base::Bind(&MediaCodecPlayer::OnDecoderDrained, media_weak_this_, + DemuxerStream::AUDIO), + base::Bind(&MediaCodecPlayer::OnStopDone, media_weak_this_, + DemuxerStream::AUDIO), + base::Bind(&MediaCodecPlayer::OnMissingKeyReported, media_weak_this_, + DemuxerStream::AUDIO), + internal_error_cb_, base::Bind(&MediaCodecPlayer::OnTimeIntervalUpdate, media_weak_this_, DemuxerStream::AUDIO))); video_decoder_.reset(new MediaCodecVideoDecoder( - GetMediaTaskRunner(), base::Bind(&MediaCodecPlayer::RequestDemuxerData, - media_weak_this_, DemuxerStream::VIDEO), + GetMediaTaskRunner(), &media_stat_->video_frame_stats(), + base::Bind(&MediaCodecPlayer::RequestDemuxerData, media_weak_this_, + DemuxerStream::VIDEO), base::Bind(&MediaCodecPlayer::OnStarvation, media_weak_this_, DemuxerStream::VIDEO), - base::Bind(&MediaCodecPlayer::OnStopDone, media_weak_this_), error_cb_, - MediaCodecDecoder::SetTimeCallback(), // null callback - base::Bind(&MediaCodecPlayer::OnVideoResolutionChanged, media_weak_this_), - base::Bind(&MediaCodecPlayer::OnVideoCodecCreated, media_weak_this_))); + base::Bind(&MediaCodecPlayer::OnDecoderDrained, media_weak_this_, + DemuxerStream::VIDEO), + base::Bind(&MediaCodecPlayer::OnStopDone, media_weak_this_, + DemuxerStream::VIDEO), + base::Bind(&MediaCodecPlayer::OnMissingKeyReported, media_weak_this_, + DemuxerStream::VIDEO), + internal_error_cb_, + base::Bind(&MediaCodecPlayer::OnTimeIntervalUpdate, media_weak_this_, + DemuxerStream::VIDEO), + base::Bind(&MediaCodecPlayer::OnVideoResolutionChanged, + media_weak_this_))); } -bool MediaCodecPlayer::AudioFinished() { +bool MediaCodecPlayer::AudioFinished() const { return audio_decoder_->IsCompleted() || !audio_decoder_->HasStream(); } -bool MediaCodecPlayer::VideoFinished() { +bool MediaCodecPlayer::VideoFinished() const { return video_decoder_->IsCompleted() || !video_decoder_->HasStream(); } @@ -714,13 +1406,17 @@ base::TimeDelta MediaCodecPlayer::GetInterpolatedTime() { const char* MediaCodecPlayer::AsString(PlayerState state) { switch (state) { - RETURN_STRING(STATE_PAUSED); - RETURN_STRING(STATE_WAITING_FOR_CONFIG); - RETURN_STRING(STATE_PREFETCHING); - RETURN_STRING(STATE_PLAYING); - RETURN_STRING(STATE_STOPPING); - RETURN_STRING(STATE_WAITING_FOR_SURFACE); - RETURN_STRING(STATE_ERROR); + RETURN_STRING(kStatePaused); + RETURN_STRING(kStateWaitingForConfig); + RETURN_STRING(kStateWaitingForPermission); + RETURN_STRING(kStatePrefetching); + RETURN_STRING(kStatePlaying); + RETURN_STRING(kStateStopping); + RETURN_STRING(kStateWaitingForSurface); + RETURN_STRING(kStateWaitingForKey); + RETURN_STRING(kStateWaitingForMediaCrypto); + RETURN_STRING(kStateWaitingForSeek); + RETURN_STRING(kStateError); } return nullptr; // crash early } |