| // 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/audio_renderer_internal.h" |
| |
| #include <algorithm> |
| |
| #include "starboard/memory.h" |
| |
| namespace starboard { |
| namespace shared { |
| namespace starboard { |
| namespace player { |
| |
| namespace { |
| // TODO: This should be retrieved from the decoder. |
| // TODO: Make it not dependent on the frame size of AAC and HE-AAC. |
| const int kMaxFramesPerAccessUnit = 1024 * 2; |
| } // namespace |
| |
| AudioRenderer::AudioRenderer(AudioDecoder* decoder, |
| const SbMediaAudioHeader& audio_header) |
| : channels_(audio_header.number_of_channels), |
| paused_(true), |
| seeking_(false), |
| seeking_to_pts_(0), |
| frame_buffer_(kMaxCachedFrames * audio_header.number_of_channels), |
| frames_in_buffer_(0), |
| offset_in_frames_(0), |
| frames_consumed_(0), |
| end_of_stream_reached_(false), |
| decoder_(decoder), |
| audio_sink_(kSbAudioSinkInvalid) { |
| SB_DCHECK(decoder_ != NULL); |
| frame_buffers_[0] = &frame_buffer_[0]; |
| } |
| |
| AudioRenderer::~AudioRenderer() { |
| if (audio_sink_ != kSbAudioSinkInvalid) { |
| SbAudioSinkDestroy(audio_sink_); |
| } |
| delete decoder_; |
| } |
| |
| void AudioRenderer::WriteSample(const InputBuffer& input_buffer) { |
| if (end_of_stream_reached_) { |
| SB_LOG(ERROR) << "Appending audio sample at " << input_buffer.pts() |
| << " after EOS reached."; |
| return; |
| } |
| |
| SbMediaTime input_pts = input_buffer.pts(); |
| std::vector<float> decoded_audio; |
| decoder_->Decode(input_buffer, &decoded_audio); |
| if (decoded_audio.empty()) { |
| SB_DLOG(ERROR) << "decoded_audio contains no frames."; |
| return; |
| } |
| |
| { |
| ScopedLock lock(mutex_); |
| if (seeking_) { |
| if (input_pts < seeking_to_pts_) { |
| return; |
| } |
| } |
| |
| AppendFrames(&decoded_audio[0], decoded_audio.size() / channels_); |
| |
| if (seeking_ && frame_buffer_.size() > kPrerollFrames * channels_) { |
| 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, kSbMediaAudioSampleTypeFloat32, |
| kSbMediaAudioFrameStorageTypeInterleaved, |
| reinterpret_cast<SbAudioSinkFrameBuffers>(frame_buffers_), |
| kMaxCachedFrames, &AudioRenderer::UpdateSourceStatusFunc, |
| &AudioRenderer::ConsumeFramesFunc, this); |
| } |
| } |
| |
| void AudioRenderer::WriteEndOfStream() { |
| SB_LOG_IF(WARNING, end_of_stream_reached_) |
| << "Try to write EOS after EOS is reached"; |
| if (end_of_stream_reached_) { |
| return; |
| } |
| end_of_stream_reached_ = true; |
| decoder_->WriteEndOfStream(); |
| |
| ScopedLock lock(mutex_); |
| // 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 AudioRenderer::Play() { |
| ScopedLock lock(mutex_); |
| paused_ = false; |
| } |
| |
| void AudioRenderer::Pause() { |
| ScopedLock lock(mutex_); |
| paused_ = true; |
| } |
| |
| void AudioRenderer::Seek(SbMediaTime seek_to_pts) { |
| SB_DCHECK(seek_to_pts >= 0); |
| |
| SbAudioSinkDestroy(audio_sink_); |
| 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; |
| end_of_stream_reached_ = false; |
| |
| decoder_->Reset(); |
| return; |
| } |
| |
| bool AudioRenderer::IsEndOfStreamPlayed() const { |
| return end_of_stream_reached_ && frames_in_buffer_ == 0; |
| } |
| |
| bool AudioRenderer::CanAcceptMoreData() const { |
| ScopedLock lock(mutex_); |
| return frames_in_buffer_ <= kMaxCachedFrames - kMaxFramesPerAccessUnit && |
| !end_of_stream_reached_; |
| } |
| |
| bool AudioRenderer::IsSeekingInProgress() const { |
| return seeking_; |
| } |
| |
| SbMediaTime AudioRenderer::GetCurrentTime() { |
| if (seeking_) { |
| return seeking_to_pts_; |
| } |
| return seeking_to_pts_ + |
| frames_consumed_ * kSbMediaTimeSecond / |
| decoder_->GetSamplesPerSecond(); |
| } |
| |
| // static |
| void AudioRenderer::UpdateSourceStatusFunc(int* frames_in_buffer, |
| int* offset_in_frames, |
| bool* is_playing, |
| bool* is_eos_reached, |
| void* context) { |
| AudioRenderer* audio_renderer = reinterpret_cast<AudioRenderer*>(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 AudioRenderer::ConsumeFramesFunc(int frames_consumed, void* context) { |
| AudioRenderer* audio_renderer = reinterpret_cast<AudioRenderer*>(context); |
| SB_DCHECK(audio_renderer); |
| |
| audio_renderer->ConsumeFrames(frames_consumed); |
| } |
| |
| void AudioRenderer::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_reached_; |
| |
| 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 AudioRenderer::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; |
| } |
| |
| void AudioRenderer::AppendFrames(const float* source_buffer, |
| int frames_to_append) { |
| SB_DCHECK(frames_in_buffer_ + frames_to_append <= kMaxCachedFrames); |
| |
| 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 * channels_], source_buffer, |
| (kMaxCachedFrames - offset_to_append) * sizeof(float) * channels_); |
| source_buffer += (kMaxCachedFrames - offset_to_append) * channels_; |
| frames_to_append -= kMaxCachedFrames - offset_to_append; |
| frames_in_buffer_ += kMaxCachedFrames - offset_to_append; |
| offset_to_append = 0; |
| } |
| SbMemoryCopy(&frame_buffer_[offset_to_append * channels_], source_buffer, |
| frames_to_append * sizeof(float) * channels_); |
| frames_in_buffer_ += frames_to_append; |
| } |
| |
| } // namespace player |
| } // namespace starboard |
| } // namespace shared |
| } // namespace starboard |