// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef MEDIA_BASE_PIPELINE_H_ #define MEDIA_BASE_PIPELINE_H_ #include <string> #include "base/gtest_prod_util.h" #include "base/synchronization/condition_variable.h" #include "base/synchronization/lock.h" #include "base/threading/thread_checker.h" #include "base/time/default_tick_clock.h" #include "media/base/audio_renderer.h" #include "media/base/demuxer.h" #include "media/base/media_export.h" #include "media/base/pipeline_status.h" #include "media/base/ranges.h" #include "media/base/serial_runner.h" #include "ui/gfx/size.h" namespace base { class SingleThreadTaskRunner; class TimeDelta; } namespace media { class Clock; class FilterCollection; class MediaLog; class TextRenderer; class TextTrackConfig; class VideoRenderer; // Metadata describing a pipeline once it has been initialized. struct PipelineMetadata { PipelineMetadata() : has_audio(false), has_video(false) {} bool has_audio; bool has_video; gfx::Size natural_size; }; typedef base::Callback<void(PipelineMetadata)> PipelineMetadataCB; // Pipeline runs the media pipeline. Filters are created and called on the // task runner injected into this object. Pipeline works like a state // machine to perform asynchronous initialization, pausing, seeking and playing. // // Here's a state diagram that describes the lifetime of this object. // // [ *Created ] [ Any State ] // | Start() | Stop() / SetError() // V V // [ InitXXX (for each filter) ] [ Stopping ] // | | // V V // [ InitPreroll ] [ Stopped ] // | // V // [ Starting ] <-- [ Seeking ] // | ^ // V | // [ Started ] ----------' // Seek() // // Initialization is a series of state transitions from "Created" through each // filter initialization state. When all filter initialization states have // completed, we are implicitly in a "Paused" state. At that point we simulate // a Seek() to the beginning of the media to give filters a chance to preroll. // From then on the normal Seek() transitions are carried out and we start // playing the media. // // If any error ever happens, this object will transition to the "Error" state // from any state. If Stop() is ever called, this object will transition to // "Stopped" state. class MEDIA_EXPORT Pipeline : public DataSourceHost, public DemuxerHost { public: // Constructs a media pipeline that will execute on |task_runner|. Pipeline(const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, MediaLog* media_log); virtual ~Pipeline(); // Build a pipeline to using the given filter collection to construct a filter // chain, executing |seek_cb| when the initial seek/preroll has completed. // // |filter_collection| must be a complete collection containing a demuxer, // audio/video decoders, and audio/video renderers. Failing to do so will // result in a crash. // // The following permanent callbacks will be executed as follows up until // Stop() has completed: // |ended_cb| will be executed whenever the media reaches the end. // |error_cb| will be executed whenever an error occurs but hasn't been // reported already through another callback. // |metadata_cb| will be executed when the content duration, container video // size, start time, and whether the content has audio and/or // video in supported formats are known. // |preroll_completed_cb| will be executed when all renderers have buffered // enough data to satisfy preroll and are ready to // start playback. // |duration_change_cb| optional callback that will be executed whenever the // presentation duration changes. // It is an error to call this method after the pipeline has already started. void Start(scoped_ptr<FilterCollection> filter_collection, const base::Closure& ended_cb, const PipelineStatusCB& error_cb, const PipelineStatusCB& seek_cb, const PipelineMetadataCB& metadata_cb, const base::Closure& preroll_completed_cb, const base::Closure& duration_change_cb); // Asynchronously stops the pipeline, executing |stop_cb| when the pipeline // teardown has completed. // // Stop() must complete before destroying the pipeline. It it permissible to // call Stop() at any point during the lifetime of the pipeline. // // It is safe to delete the pipeline during the execution of |stop_cb|. void Stop(const base::Closure& stop_cb); // Attempt to seek to the position specified by time. |seek_cb| will be // executed when the all filters in the pipeline have processed the seek. // // Clients are expected to call GetMediaTime() to check whether the seek // succeeded. // // It is an error to call this method if the pipeline has not started. void Seek(base::TimeDelta time, const PipelineStatusCB& seek_cb); // Returns true if the pipeline has been started via Start(). If IsRunning() // returns true, it is expected that Stop() will be called before destroying // the pipeline. bool IsRunning() const; // Gets the current playback rate of the pipeline. When the pipeline is // started, the playback rate will be 0.0f. A rate of 1.0f indicates // that the pipeline is rendering the media at the standard rate. Valid // values for playback rate are >= 0.0f. float GetPlaybackRate() const; // Attempt to adjust the playback rate. Setting a playback rate of 0.0f pauses // all rendering of the media. A rate of 1.0f indicates a normal playback // rate. Values for the playback rate must be greater than or equal to 0.0f. // // TODO(scherkus): What about maximum rate? Does HTML5 specify a max? void SetPlaybackRate(float playback_rate); // Gets the current volume setting being used by the audio renderer. When // the pipeline is started, this value will be 1.0f. Valid values range // from 0.0f to 1.0f. float GetVolume() const; // Attempt to set the volume of the audio renderer. Valid values for volume // range from 0.0f (muted) to 1.0f (full volume). This value affects all // channels proportionately for multi-channel audio streams. void SetVolume(float volume); // Returns the current media playback time, which progresses from 0 until // GetMediaDuration(). base::TimeDelta GetMediaTime() const; // Get approximate time ranges of buffered media. Ranges<base::TimeDelta> GetBufferedTimeRanges(); // Get the duration of the media in microseconds. If the duration has not // been determined yet, then returns 0. base::TimeDelta GetMediaDuration() const; // Get the total size of the media file. If the size has not yet been // determined or can not be determined, this value is 0. int64 GetTotalBytes() const; // Return true if loading progress has been made since the last time this // method was called. bool DidLoadingProgress() const; // Gets the current pipeline statistics. PipelineStatistics GetStatistics() const; void SetClockForTesting(Clock* clock); void SetErrorForTesting(PipelineStatus status); private: FRIEND_TEST_ALL_PREFIXES(PipelineTest, GetBufferedTimeRanges); FRIEND_TEST_ALL_PREFIXES(PipelineTest, DisableAudioRenderer); FRIEND_TEST_ALL_PREFIXES(PipelineTest, DisableAudioRendererDuringInit); FRIEND_TEST_ALL_PREFIXES(PipelineTest, EndedCallback); FRIEND_TEST_ALL_PREFIXES(PipelineTest, AudioStreamShorterThanVideo); friend class MediaLog; // Pipeline states, as described above. enum State { kCreated, kInitDemuxer, kInitAudioRenderer, kInitVideoRenderer, kInitPrerolling, kSeeking, kStarting, kStarted, kStopping, kStopped, }; // Updates |state_|. All state transitions should use this call. void SetState(State next_state); static const char* GetStateString(State state); State GetNextState() const; // Helper method that runs & resets |seek_cb_| and resets |seek_timestamp_| // and |seek_pending_|. void FinishSeek(); // DataSourceHost (by way of DemuxerHost) implementation. virtual void SetTotalBytes(int64 total_bytes) OVERRIDE; virtual void AddBufferedByteRange(int64 start, int64 end) OVERRIDE; virtual void AddBufferedTimeRange(base::TimeDelta start, base::TimeDelta end) OVERRIDE; // DemuxerHost implementaion. virtual void SetDuration(base::TimeDelta duration) OVERRIDE; virtual void OnDemuxerError(PipelineStatus error) OVERRIDE; virtual void AddTextStream(DemuxerStream* text_stream, const TextTrackConfig& config) OVERRIDE; virtual void RemoveTextStream(DemuxerStream* text_stream) OVERRIDE; // Initiates teardown sequence in response to a runtime error. // // Safe to call from any thread. void SetError(PipelineStatus error); // Callbacks executed when a renderer has ended. void OnAudioRendererEnded(); void OnVideoRendererEnded(); void OnTextRendererEnded(); // Callback executed by filters to update statistics. void OnUpdateStatistics(const PipelineStatistics& stats); // Callback executed by audio renderer when it has been disabled. void OnAudioDisabled(); // Callback executed by audio renderer to update clock time. void OnAudioTimeUpdate(base::TimeDelta time, base::TimeDelta max_time); // Callback executed by video renderer to update clock time. void OnVideoTimeUpdate(base::TimeDelta max_time); // The following "task" methods correspond to the public methods, but these // methods are run as the result of posting a task to the Pipeline's // task runner. void StartTask(); // Stops and destroys all filters, placing the pipeline in the kStopped state. void StopTask(const base::Closure& stop_cb); // Carries out stopping and destroying all filters, placing the pipeline in // the kStopped state. void ErrorChangedTask(PipelineStatus error); // Carries out notifying filters that the playback rate has changed. void PlaybackRateChangedTask(float playback_rate); // Carries out notifying filters that the volume has changed. void VolumeChangedTask(float volume); // Carries out notifying filters that we are seeking to a new timestamp. void SeekTask(base::TimeDelta time, const PipelineStatusCB& seek_cb); // Handles audio/video/text ended logic and running |ended_cb_|. void DoAudioRendererEnded(); void DoVideoRendererEnded(); void DoTextRendererEnded(); void RunEndedCallbackIfNeeded(); // Carries out disabling the audio renderer. void AudioDisabledTask(); // Carries out adding a new text stream to the text renderer. void AddTextStreamTask(DemuxerStream* text_stream, const TextTrackConfig& config); // Carries out removing a text stream from the text renderer. void RemoveTextStreamTask(DemuxerStream* text_stream); // Kicks off initialization for each media object, executing |done_cb| with // the result when completed. void InitializeDemuxer(const PipelineStatusCB& done_cb); void InitializeAudioRenderer(const PipelineStatusCB& done_cb); void InitializeVideoRenderer(const PipelineStatusCB& done_cb); // Kicks off destroying filters. Called by StopTask() and ErrorChangedTask(). // When we start to tear down the pipeline, we will consider two cases: // 1. when pipeline has not been initialized, we will transit to stopping // state first. // 2. when pipeline has been initialized, we will first transit to pausing // => flushing => stopping => stopped state. // This will remove the race condition during stop between filters. void TearDownPipeline(); // Compute the time corresponding to a byte offset. base::TimeDelta TimeForByteOffset_Locked(int64 byte_offset) const; void OnStateTransition(PipelineStatus status); void StateTransitionTask(PipelineStatus status); // Initiates an asynchronous preroll call sequence executing |done_cb| // with the final status when completed. void DoInitialPreroll(const PipelineStatusCB& done_cb); // Initiates an asynchronous pause-flush-seek-preroll call sequence // executing |done_cb| with the final status when completed. // // TODO(scherkus): Prerolling should be separate from seeking so we can report // finer grained ready states (HAVE_CURRENT_DATA vs. HAVE_FUTURE_DATA) // indepentent from seeking. void DoSeek(base::TimeDelta seek_timestamp, const PipelineStatusCB& done_cb); // Updates playback rate and volume and initiates an asynchronous play call // sequence executing |done_cb| with the final status when completed. void DoPlay(const PipelineStatusCB& done_cb); // Initiates an asynchronous pause-flush-stop call sequence executing // |done_cb| when completed. void DoStop(const PipelineStatusCB& done_cb); void OnStopCompleted(PipelineStatus status); void OnAudioUnderflow(); void StartClockIfWaitingForTimeUpdate_Locked(); // Task runner used to execute pipeline tasks. scoped_refptr<base::SingleThreadTaskRunner> task_runner_; // MediaLog to which to log events. scoped_refptr<MediaLog> media_log_; // Lock used to serialize access for the following data members. mutable base::Lock lock_; // Whether or not the pipeline is running. bool running_; // Amount of available buffered data. Set by filters. Ranges<int64> buffered_byte_ranges_; Ranges<base::TimeDelta> buffered_time_ranges_; // True when AddBufferedByteRange() has been called more recently than // DidLoadingProgress(). mutable bool did_loading_progress_; // Total size of the media. Set by filters. int64 total_bytes_; // Current volume level (from 0.0f to 1.0f). This value is set immediately // via SetVolume() and a task is dispatched on the task runner to notify the // filters. float volume_; // Current playback rate (>= 0.0f). This value is set immediately via // SetPlaybackRate() and a task is dispatched on the task runner to notify // the filters. float playback_rate_; // base::TickClock used by |clock_|. base::DefaultTickClock default_tick_clock_; // Reference clock. Keeps track of current playback time. Uses system // clock and linear interpolation, but can have its time manually set // by filters. scoped_ptr<Clock> clock_; // If this value is set to true, then |clock_| is paused and we are waiting // for an update of the clock greater than or equal to the elapsed time to // start the clock. bool waiting_for_clock_update_; // Status of the pipeline. Initialized to PIPELINE_OK which indicates that // the pipeline is operating correctly. Any other value indicates that the // pipeline is stopped or is stopping. Clients can call the Stop() method to // reset the pipeline state, and restore this to PIPELINE_OK. PipelineStatus status_; // The following data members are only accessed by tasks posted to // |task_runner_|. // Member that tracks the current state. State state_; // Whether we've received the audio/video/text ended events. bool audio_ended_; bool video_ended_; bool text_ended_; // Set to true in DisableAudioRendererTask(). bool audio_disabled_; // Temporary callback used for Start() and Seek(). PipelineStatusCB seek_cb_; // Temporary callback used for Stop(). base::Closure stop_cb_; // Permanent callbacks passed in via Start(). base::Closure ended_cb_; PipelineStatusCB error_cb_; PipelineMetadataCB metadata_cb_; base::Closure preroll_completed_cb_; base::Closure duration_change_cb_; // Contains the demuxer and renderers to use when initializing. scoped_ptr<FilterCollection> filter_collection_; // Holds the initialized demuxer. Used for seeking. Owned by client. Demuxer* demuxer_; // Holds the initialized renderers. Used for setting the volume, // playback rate, and determining when playback has finished. scoped_ptr<AudioRenderer> audio_renderer_; scoped_ptr<VideoRenderer> video_renderer_; scoped_ptr<TextRenderer> text_renderer_; PipelineStatistics statistics_; // Time of pipeline creation; is non-zero only until the pipeline first // reaches "kStarted", at which point it is used & zeroed out. base::TimeTicks creation_time_; scoped_ptr<SerialRunner> pending_callbacks_; base::ThreadChecker thread_checker_; DISALLOW_COPY_AND_ASSIGN(Pipeline); }; } // namespace media #endif // MEDIA_BASE_PIPELINE_H_