blob: 940bb83e73a74cb4bb747f85e9e45e5959e2c84b [file] [log] [blame]
// Copyright 2022 The Cobalt Authors. 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/uwp/audio_renderer_passthrough.h"
#include <algorithm>
#include <string>
#include "starboard/common/log.h"
#include "starboard/common/string.h"
namespace starboard {
namespace shared {
namespace uwp {
namespace {
int CodecToIecSampleRate(SbMediaAudioCodec codec) {
switch (codec) {
case kSbMediaAudioCodecAc3:
return 48000;
case kSbMediaAudioCodecEac3:
return 192000;
default:
SB_NOTREACHED();
return 0;
}
}
} // namespace
AudioRendererPassthrough::AudioRendererPassthrough(
scoped_ptr<AudioDecoder> audio_decoder,
const AudioStreamInfo& audio_stream_info)
: channels_(audio_stream_info.number_of_channels),
codec_(audio_stream_info.codec),
iec_sample_rate_(CodecToIecSampleRate(audio_stream_info.codec)),
decoder_(audio_decoder.Pass()),
sink_(new WASAPIAudioSink),
process_audio_buffers_job_(
std::bind(&AudioRendererPassthrough::ProcessAudioBuffers, this)) {
SB_DCHECK(codec_ == kSbMediaAudioCodecAc3 ||
codec_ == kSbMediaAudioCodecEac3);
SB_DCHECK(decoder_);
QueryPerformanceFrequency(&performance_frequency_);
SB_DCHECK(performance_frequency_.QuadPart > 0);
SB_LOG(INFO) << "Creating AudioRendererPassthrough with " << channels_
<< " channels.";
}
AudioRendererPassthrough::~AudioRendererPassthrough() {
SB_DCHECK(BelongsToCurrentThread());
SB_LOG(INFO) << "Destroying AudioRendererPassthrough with " << channels_
<< " channels.";
}
void AudioRendererPassthrough::Initialize(const ErrorCB& error_cb,
const PrerolledCB& prerolled_cb,
const EndedCB& ended_cb) {
SB_DCHECK(BelongsToCurrentThread());
SB_DCHECK(error_cb);
SB_DCHECK(prerolled_cb);
SB_DCHECK(ended_cb);
SB_DCHECK(!error_cb_);
SB_DCHECK(!prerolled_cb_);
SB_DCHECK(!ended_cb_);
error_cb_ = error_cb;
prerolled_cb_ = prerolled_cb;
ended_cb_ = ended_cb;
decoder_->Initialize(
std::bind(&AudioRendererPassthrough::OnDecoderOutput, this), error_cb);
if (!sink_->Initialize(channels_, iec_sample_rate_, codec_)) {
error_cb_(kSbPlayerErrorDecode, "failed to start audio sink");
}
}
void AudioRendererPassthrough::WriteSamples(const InputBuffers& input_buffers) {
SB_DCHECK(BelongsToCurrentThread());
SB_DCHECK(input_buffers.size() == 1);
SB_DCHECK(input_buffers[0]);
SB_DCHECK(can_accept_more_data_.load());
if (end_of_stream_written_.load()) {
SB_LOG(ERROR) << "Appending audio sample at "
<< input_buffers[0]->timestamp() << " after EOS reached.";
return;
}
can_accept_more_data_.store(false);
decoder_->Decode(
input_buffers,
std::bind(&AudioRendererPassthrough::OnDecoderConsumed, this));
}
void AudioRendererPassthrough::WriteEndOfStream() {
SB_DCHECK(BelongsToCurrentThread());
if (end_of_stream_written_.load()) {
SB_LOG(ERROR) << "Try to write EOS after EOS is reached";
return;
}
end_of_stream_written_.store(true);
decoder_->WriteEndOfStream();
}
void AudioRendererPassthrough::SetVolume(double volume) {
SB_DCHECK(BelongsToCurrentThread());
sink_->SetVolume(volume);
}
bool AudioRendererPassthrough::IsEndOfStreamWritten() const {
SB_DCHECK(BelongsToCurrentThread());
return end_of_stream_written_.load();
}
bool AudioRendererPassthrough::IsEndOfStreamPlayed() const {
SB_DCHECK(BelongsToCurrentThread());
return end_of_stream_played_.load();
}
bool AudioRendererPassthrough::CanAcceptMoreData() const {
SB_DCHECK(BelongsToCurrentThread());
return !end_of_stream_written_.load() &&
pending_inputs_.size() < kMaxDecodedAudios &&
can_accept_more_data_.load();
}
void AudioRendererPassthrough::Play() {
SB_DCHECK(BelongsToCurrentThread());
ScopedLock lock(mutex_);
paused_ = false;
sink_->Play();
}
void AudioRendererPassthrough::Pause() {
SB_DCHECK(BelongsToCurrentThread());
ScopedLock lock(mutex_);
paused_ = true;
sink_->Pause();
}
void AudioRendererPassthrough::SetPlaybackRate(double playback_rate) {
SB_DCHECK(BelongsToCurrentThread());
if (playback_rate > 0.0 && playback_rate != 1.0) {
std::string error_message = ::starboard::FormatString(
"Playback rate %f is not supported", playback_rate);
error_cb_(kSbPlayerErrorDecode, error_message);
return;
}
ScopedLock lock(mutex_);
playback_rate_ = playback_rate;
sink_->SetPlaybackRate(playback_rate);
}
void AudioRendererPassthrough::Seek(SbTime seek_to_time) {
SB_DCHECK(BelongsToCurrentThread());
SB_DCHECK(seek_to_time >= 0);
{
ScopedLock lock(mutex_);
seeking_to_time_ = std::max<SbTime>(seek_to_time, 0);
seeking_ = true;
}
total_frames_sent_to_sink_ = 0;
can_accept_more_data_.store(true);
process_audio_buffers_job_token_.ResetToInvalid();
total_buffers_sent_to_sink_ = 0;
end_of_stream_written_.store(false);
end_of_stream_played_.store(false);
pending_inputs_ = std::queue<scoped_refptr<DecodedAudio>>();
sink_->Reset();
decoder_->Reset();
CancelPendingJobs();
}
SbTime AudioRendererPassthrough::GetCurrentMediaTime(bool* is_playing,
bool* is_eos_played,
bool* is_underflow,
double* playback_rate) {
SB_DCHECK(is_playing);
SB_DCHECK(is_eos_played);
SB_DCHECK(is_underflow);
SB_DCHECK(playback_rate);
ScopedLock lock(mutex_);
*is_playing = !paused_ && !seeking_;
*is_eos_played = end_of_stream_played_.load();
*is_underflow = false; // TODO: Support underflow
*playback_rate = playback_rate_;
if (seeking_) {
return seeking_to_time_;
}
uint64_t sink_playback_time_updated_at;
SbTime sink_playback_time = static_cast<SbTime>(
sink_->GetCurrentPlaybackTime(&sink_playback_time_updated_at));
if (sink_playback_time <= 0) {
if (sink_playback_time < 0) {
error_cb_(kSbPlayerErrorDecode,
"Error obtaining playback time from WASAPI sink");
}
return seeking_to_time_;
}
SbTime media_time = seeking_to_time_ + sink_playback_time;
if (!sink_->playing()) {
return media_time;
}
return media_time +
CalculateElapsedPlaybackTime(sink_playback_time_updated_at);
}
void AudioRendererPassthrough::OnDecoderConsumed() {
SB_DCHECK(BelongsToCurrentThread());
can_accept_more_data_.store(true);
}
void AudioRendererPassthrough::OnDecoderOutput() {
SB_DCHECK(BelongsToCurrentThread());
int samples_per_second = 0;
scoped_refptr<DecodedAudio> decoded_audio =
decoder_->Read(&samples_per_second);
SB_DCHECK(decoded_audio);
pending_inputs_.push(decoded_audio);
if (process_audio_buffers_job_token_.is_valid()) {
RemoveJobByToken(process_audio_buffers_job_token_);
process_audio_buffers_job_token_.ResetToInvalid();
}
ProcessAudioBuffers();
}
void AudioRendererPassthrough::ProcessAudioBuffers() {
SB_DCHECK(BelongsToCurrentThread());
SB_DCHECK(!pending_inputs_.empty());
process_audio_buffers_job_token_.ResetToInvalid();
SbTime process_audio_buffers_job_delay = 5 * kSbTimeMillisecond;
scoped_refptr<DecodedAudio> decoded_audio = pending_inputs_.front();
SB_DCHECK(decoded_audio);
if (decoded_audio->is_end_of_stream()) {
SB_DCHECK(end_of_stream_written_.load());
ScopedLock lock(mutex_);
if (seeking_) {
seeking_ = false;
Schedule(prerolled_cb_);
}
pending_inputs_.pop();
SB_DCHECK(pending_inputs_.empty());
} else {
ScopedLock lock(mutex_);
while (seeking_ && decoded_audio &&
CalculateLastOutputTime(decoded_audio) < seeking_to_time_) {
pending_inputs_.pop();
decoded_audio = pending_inputs_.empty() ? scoped_refptr<DecodedAudio>()
: pending_inputs_.front();
}
if (decoded_audio && TryToWriteAudioBufferToSink(decoded_audio)) {
pending_inputs_.pop();
process_audio_buffers_job_delay = 0;
if (seeking_ && total_buffers_sent_to_sink_ >= kNumPrerollDecodedAudios) {
seeking_ = false;
Schedule(prerolled_cb_);
}
}
}
if (!pending_inputs_.empty()) {
process_audio_buffers_job_token_ =
Schedule(process_audio_buffers_job_, process_audio_buffers_job_delay);
return;
}
if (end_of_stream_written_.load() && !end_of_stream_played_.load()) {
TryToEndStream();
}
}
bool AudioRendererPassthrough::TryToWriteAudioBufferToSink(
scoped_refptr<DecodedAudio> decoded_audio) {
SB_DCHECK(BelongsToCurrentThread());
SB_DCHECK(decoded_audio);
bool buffer_written = sink_->WriteBuffer(decoded_audio);
if (buffer_written && !decoded_audio->is_end_of_stream()) {
total_frames_sent_to_sink_ += decoded_audio->frames();
total_buffers_sent_to_sink_++;
}
return buffer_written;
}
void AudioRendererPassthrough::TryToEndStream() {
bool is_playing, is_eos_played, is_underflow;
double playback_rate;
int64_t total_frames_played_by_sink =
GetCurrentMediaTime(&is_playing, &is_eos_played, &is_underflow,
&playback_rate) *
iec_sample_rate_ / kSbTimeSecond;
// Wait for the audio sink to output the remaining frames before calling
// Pause().
if (total_frames_played_by_sink >= total_frames_sent_to_sink_) {
sink_->Pause();
end_of_stream_played_.store(true);
ended_cb_();
return;
}
Schedule(std::bind(&AudioRendererPassthrough::TryToEndStream, this),
5 * kSbTimeMillisecond);
}
SbTime AudioRendererPassthrough::CalculateElapsedPlaybackTime(
uint64_t update_time) {
LARGE_INTEGER current_time;
QueryPerformanceCounter(&current_time);
// Convert current performance counter timestamp to units of 100 nanoseconds.
// https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclock-getposition#remarks
uint64_t current_time_converted =
static_cast<double>(current_time.QuadPart) *
(10000000.0 / static_cast<double>(performance_frequency_.QuadPart));
SB_DCHECK(current_time_converted >= update_time);
// Convert elapsed time to SbTime.
return ((current_time_converted - update_time) * 100) /
kSbTimeNanosecondsPerMicrosecond;
}
SbTime AudioRendererPassthrough::CalculateLastOutputTime(
scoped_refptr<DecodedAudio>& decoded_audio) {
return decoded_audio->timestamp() +
(decoded_audio->frames() / iec_sample_rate_ * kSbTimeSecond);
}
} // namespace uwp
} // namespace shared
} // namespace starboard