| // Copyright 2016 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h" |
| |
| #include <algorithm> |
| |
| #include "starboard/memory.h" |
| #include "starboard/shared/starboard/audio_sink/audio_sink_internal.h" |
| #include "starboard/shared/starboard/player/closure.h" |
| |
| namespace starboard { |
| namespace shared { |
| namespace starboard { |
| namespace player { |
| namespace filter { |
| |
| AudioRendererImpl::AudioRendererImpl(JobQueue* job_queue, |
| scoped_ptr<AudioDecoder> decoder, |
| const SbMediaAudioHeader& audio_header) |
| : job_queue_(job_queue), |
| channels_(audio_header.number_of_channels), |
| bytes_per_frame_( |
| (decoder->GetSampleType() == kSbMediaAudioSampleTypeInt16 ? 2 : 4) * |
| channels_), |
| playback_rate_(1.0), |
| paused_(true), |
| seeking_(false), |
| seeking_to_pts_(0), |
| frame_buffer_(kMaxCachedFrames * bytes_per_frame_), |
| frames_in_buffer_(0), |
| offset_in_frames_(0), |
| frames_consumed_(0), |
| frames_consumed_set_at_(SbTimeGetMonotonicNow()), |
| end_of_stream_written_(false), |
| end_of_stream_decoded_(false), |
| decoder_(decoder.Pass()), |
| audio_sink_(kSbAudioSinkInvalid) { |
| SB_DCHECK(job_queue != NULL); |
| SB_DCHECK(decoder_ != NULL); |
| SB_DCHECK(job_queue_->BelongsToCurrentThread()); |
| |
| frame_buffers_[0] = &frame_buffer_[0]; |
| |
| // TODO: The audio sink on Android is currently broken on certain devices, |
| // which causes all of playback to hang. Log it for now, so we can tell |
| // when it happens, but this should be removed once the sink is fixed. |
| #if defined(NDEBUG) |
| const bool kLogFramesConsumed = false; |
| #else |
| const bool kLogFramesConsumed = true; |
| #endif |
| if (kLogFramesConsumed) { |
| log_frames_consumed_closure_ = |
| Bind(&AudioRendererImpl::LogFramesConsumed, this); |
| job_queue_->Schedule(log_frames_consumed_closure_, kSbTimeSecond); |
| } |
| } |
| |
| AudioRendererImpl::~AudioRendererImpl() { |
| SB_DCHECK(job_queue_->BelongsToCurrentThread()); |
| |
| if (audio_sink_ != kSbAudioSinkInvalid) { |
| SbAudioSinkDestroy(audio_sink_); |
| } |
| |
| if (read_from_decoder_closure_.is_valid()) { |
| job_queue_->Remove(read_from_decoder_closure_); |
| } |
| |
| if (log_frames_consumed_closure_.is_valid()) { |
| job_queue_->Remove(log_frames_consumed_closure_); |
| } |
| } |
| |
| void AudioRendererImpl::WriteSample(const InputBuffer& input_buffer) { |
| SB_DCHECK(job_queue_->BelongsToCurrentThread()); |
| |
| if (end_of_stream_written_) { |
| SB_LOG(ERROR) << "Appending audio sample at " << input_buffer.pts() |
| << " after EOS reached."; |
| return; |
| } |
| |
| decoder_->Decode(input_buffer); |
| |
| ScopedLock lock(mutex_); |
| if (!read_from_decoder_closure_.is_valid()) { |
| read_from_decoder_closure_ = |
| Bind(&AudioRendererImpl::ReadFromDecoder, this); |
| job_queue_->Schedule(read_from_decoder_closure_); |
| } |
| } |
| |
| void AudioRendererImpl::WriteEndOfStream() { |
| SB_DCHECK(job_queue_->BelongsToCurrentThread()); |
| |
| SB_LOG_IF(WARNING, end_of_stream_written_) |
| << "Try to write EOS after EOS is reached"; |
| if (end_of_stream_written_) { |
| return; |
| } |
| decoder_->WriteEndOfStream(); |
| |
| ScopedLock lock(mutex_); |
| end_of_stream_written_ = true; |
| // If we are seeking, we consider the seek is finished if end of stream is |
| // reached as there won't be any audio data in future. |
| if (seeking_) { |
| seeking_ = false; |
| } |
| } |
| |
| void AudioRendererImpl::Play() { |
| SB_DCHECK(job_queue_->BelongsToCurrentThread()); |
| |
| ScopedLock lock(mutex_); |
| paused_ = false; |
| } |
| |
| void AudioRendererImpl::Pause() { |
| SB_DCHECK(job_queue_->BelongsToCurrentThread()); |
| |
| ScopedLock lock(mutex_); |
| paused_ = true; |
| } |
| |
| #if SB_API_VERSION >= SB_PLAYER_SET_PLAYBACK_RATE_VERSION |
| void AudioRendererImpl::SetPlaybackRate(double playback_rate) { |
| SB_DCHECK(job_queue_->BelongsToCurrentThread()); |
| |
| playback_rate_ = playback_rate; |
| |
| if (audio_sink_) { |
| audio_sink_->SetPlaybackRate(playback_rate); |
| } |
| } |
| #endif // SB_API_VERSION >= SB_PLAYER_SET_PLAYBACK_RATE_VERSION |
| |
| void AudioRendererImpl::Seek(SbMediaTime seek_to_pts) { |
| SB_DCHECK(job_queue_->BelongsToCurrentThread()); |
| SB_DCHECK(seek_to_pts >= 0); |
| |
| SbAudioSinkDestroy(audio_sink_); |
| // Now the sink is destroyed and the callbacks will no longer be called, so |
| // the following modifications are safe without lock. |
| audio_sink_ = kSbAudioSinkInvalid; |
| |
| seeking_to_pts_ = std::max<SbMediaTime>(seek_to_pts, 0); |
| seeking_ = true; |
| frames_in_buffer_ = 0; |
| offset_in_frames_ = 0; |
| frames_consumed_ = 0; |
| frames_consumed_set_at_ = SbTimeGetMonotonicNow(); |
| end_of_stream_written_ = false; |
| end_of_stream_decoded_ = false; |
| pending_decoded_audio_ = NULL; |
| |
| decoder_->Reset(); |
| } |
| |
| bool AudioRendererImpl::IsEndOfStreamPlayed() const { |
| SB_DCHECK(job_queue_->BelongsToCurrentThread()); |
| |
| ScopedLock lock(mutex_); |
| return end_of_stream_decoded_ && frames_in_buffer_ == 0; |
| } |
| |
| bool AudioRendererImpl::CanAcceptMoreData() const { |
| SB_DCHECK(job_queue_->BelongsToCurrentThread()); |
| |
| ScopedLock lock(mutex_); |
| if (end_of_stream_written_) { |
| return false; |
| } |
| return pending_decoded_audio_ == NULL; |
| } |
| |
| bool AudioRendererImpl::IsSeekingInProgress() const { |
| SB_DCHECK(job_queue_->BelongsToCurrentThread()); |
| return seeking_; |
| } |
| |
| SbMediaTime AudioRendererImpl::GetCurrentTime() { |
| SB_DCHECK(job_queue_->BelongsToCurrentThread()); |
| |
| if (seeking_) { |
| return seeking_to_pts_; |
| } |
| return seeking_to_pts_ + |
| frames_consumed_ * kSbMediaTimeSecond / |
| decoder_->GetSamplesPerSecond(); |
| } |
| |
| void AudioRendererImpl::UpdateSourceStatus(int* frames_in_buffer, |
| int* offset_in_frames, |
| bool* is_playing, |
| bool* is_eos_reached) { |
| ScopedLock lock(mutex_); |
| |
| *is_eos_reached = end_of_stream_decoded_; |
| |
| if (paused_ || seeking_) { |
| *is_playing = false; |
| *frames_in_buffer = *offset_in_frames = 0; |
| return; |
| } |
| |
| *is_playing = true; |
| *frames_in_buffer = frames_in_buffer_; |
| *offset_in_frames = offset_in_frames_; |
| } |
| |
| void AudioRendererImpl::ConsumeFrames(int frames_consumed) { |
| ScopedLock lock(mutex_); |
| |
| SB_DCHECK(frames_consumed <= frames_in_buffer_); |
| offset_in_frames_ += frames_consumed; |
| offset_in_frames_ %= kMaxCachedFrames; |
| frames_in_buffer_ -= frames_consumed; |
| frames_consumed_ += frames_consumed; |
| frames_consumed_set_at_ = SbTimeGetMonotonicNow(); |
| |
| bool decoded_audio_available = |
| pending_decoded_audio_ || |
| (end_of_stream_written_ && !end_of_stream_decoded_); |
| if (decoded_audio_available && !read_from_decoder_closure_.is_valid()) { |
| read_from_decoder_closure_ = |
| Bind(&AudioRendererImpl::ReadFromDecoder, this); |
| job_queue_->Schedule(read_from_decoder_closure_); |
| } |
| } |
| |
| void AudioRendererImpl::LogFramesConsumed() { |
| SbTimeMonotonic time_since = |
| SbTimeGetMonotonicNow() - frames_consumed_set_at_; |
| if (time_since > kSbTimeSecond) { |
| SB_DLOG(WARNING) << "|frames_consumed_| has not been updated for " |
| << (time_since / kSbTimeSecond) << "." |
| << ((time_since / (kSbTimeSecond / 10)) % 10) |
| << " seconds, and |pending_decoded_audio_| is " |
| << (!!pending_decoded_audio_ ? "" : "not ") << "ready."; |
| } |
| job_queue_->Schedule(log_frames_consumed_closure_, kSbTimeSecond); |
| } |
| |
| // Try to read some audio data from the decoder. Note that this operation is |
| // valid across seeking. If a seek happens immediately after a ReadFromDecoder |
| // request is scheduled, the seek will reset the decoder. So the |
| // ReadFromDecoder request will not read stale data. |
| void AudioRendererImpl::ReadFromDecoder() { |
| SB_DCHECK(job_queue_->BelongsToCurrentThread()); |
| |
| ScopedLock lock(mutex_); |
| SB_DCHECK(read_from_decoder_closure_.is_valid()); |
| read_from_decoder_closure_.reset(); |
| |
| scoped_refptr<DecodedAudio> decoded_audio = |
| pending_decoded_audio_ ? pending_decoded_audio_ : decoder_->Read(); |
| pending_decoded_audio_ = NULL; |
| if (!decoded_audio) { |
| return; |
| } |
| |
| if (decoded_audio->is_end_of_stream()) { |
| SB_DCHECK(end_of_stream_written_); |
| end_of_stream_decoded_ = true; |
| return; |
| } |
| |
| if (seeking_) { |
| if (decoded_audio->pts() < seeking_to_pts_) { |
| // Discard any audio data before the seeking target. |
| return; |
| } |
| } |
| |
| if (!AppendDecodedAudio_Locked(decoded_audio)) { |
| pending_decoded_audio_ = decoded_audio; |
| return; |
| } |
| |
| if (seeking_ && frame_buffer_.size() > kPrerollFrames * bytes_per_frame_) { |
| seeking_ = false; |
| } |
| |
| // Create the audio sink if it is the first incoming AU after seeking. |
| if (audio_sink_ == kSbAudioSinkInvalid) { |
| int sample_rate = decoder_->GetSamplesPerSecond(); |
| // TODO: Implement resampler. |
| SB_DCHECK(sample_rate == |
| SbAudioSinkGetNearestSupportedSampleFrequency(sample_rate)); |
| // TODO: Handle sink creation failure. |
| audio_sink_ = SbAudioSinkCreate( |
| channels_, sample_rate, decoder_->GetSampleType(), |
| kSbMediaAudioFrameStorageTypeInterleaved, |
| reinterpret_cast<SbAudioSinkFrameBuffers>(frame_buffers_), |
| kMaxCachedFrames, &AudioRendererImpl::UpdateSourceStatusFunc, |
| &AudioRendererImpl::ConsumeFramesFunc, this); |
| #if SB_API_VERSION >= SB_PLAYER_SET_PLAYBACK_RATE_VERSION |
| audio_sink_->SetPlaybackRate(playback_rate_); |
| #endif // SB_API_VERSION >= SB_PLAYER_SET_PLAYBACK_RATE_VERSION |
| } |
| } |
| |
| // TODO: This function should be executed when lock is not acquired as it copies |
| // relatively large amount of data. |
| bool AudioRendererImpl::AppendDecodedAudio_Locked( |
| const scoped_refptr<DecodedAudio>& decoded_audio) { |
| SB_DCHECK(job_queue_->BelongsToCurrentThread()); |
| |
| const uint8_t* source_buffer = decoded_audio->buffer(); |
| int frames_to_append = decoded_audio->size() / bytes_per_frame_; |
| |
| if (frames_in_buffer_ + frames_to_append > kMaxCachedFrames) { |
| return false; |
| } |
| |
| int offset_to_append = |
| (offset_in_frames_ + frames_in_buffer_) % kMaxCachedFrames; |
| if (frames_to_append > kMaxCachedFrames - offset_to_append) { |
| SbMemoryCopy(&frame_buffer_[offset_to_append * bytes_per_frame_], |
| source_buffer, |
| (kMaxCachedFrames - offset_to_append) * bytes_per_frame_); |
| source_buffer += (kMaxCachedFrames - offset_to_append) * bytes_per_frame_; |
| frames_to_append -= kMaxCachedFrames - offset_to_append; |
| frames_in_buffer_ += kMaxCachedFrames - offset_to_append; |
| offset_to_append = 0; |
| } |
| SbMemoryCopy(&frame_buffer_[offset_to_append * bytes_per_frame_], |
| source_buffer, frames_to_append * bytes_per_frame_); |
| frames_in_buffer_ += frames_to_append; |
| |
| return true; |
| } |
| |
| // static |
| void AudioRendererImpl::UpdateSourceStatusFunc(int* frames_in_buffer, |
| int* offset_in_frames, |
| bool* is_playing, |
| bool* is_eos_reached, |
| void* context) { |
| AudioRendererImpl* audio_renderer = static_cast<AudioRendererImpl*>(context); |
| SB_DCHECK(audio_renderer); |
| SB_DCHECK(frames_in_buffer); |
| SB_DCHECK(offset_in_frames); |
| SB_DCHECK(is_playing); |
| SB_DCHECK(is_eos_reached); |
| |
| audio_renderer->UpdateSourceStatus(frames_in_buffer, offset_in_frames, |
| is_playing, is_eos_reached); |
| } |
| |
| // static |
| void AudioRendererImpl::ConsumeFramesFunc(int frames_consumed, void* context) { |
| AudioRendererImpl* audio_renderer = static_cast<AudioRendererImpl*>(context); |
| SB_DCHECK(audio_renderer); |
| |
| audio_renderer->ConsumeFrames(frames_consumed); |
| } |
| |
| } // namespace filter |
| } // namespace player |
| } // namespace starboard |
| } // namespace shared |
| } // namespace starboard |