blob: c460a7edb580b46ee85b84ad4697f68aecece788 [file] [log] [blame]
// Copyright 2019 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.
#include "cast/standalone_receiver/sdl_audio_player.h"
#include <chrono>
#include <sstream>
#include <utility>
#include "absl/types/span.h"
#include "cast/standalone_receiver/avcodec_glue.h"
#include "util/big_endian.h"
#include "util/chrono_helpers.h"
#include "util/osp_logging.h"
#include "util/trace_logging.h"
namespace openscreen {
namespace cast {
namespace {
constexpr char kAudioMediaType[] = "audio";
constexpr SDL_AudioFormat kSDLAudioFormatUnknown = 0;
bool SDLAudioSpecsAreDifferent(const SDL_AudioSpec& a, const SDL_AudioSpec& b) {
return a.freq != b.freq || a.format != b.format || a.channels != b.channels ||
a.samples != b.samples;
}
// Convert |num_channels| separate |planes| of audio, each containing
// |num_samples| samples, into a single array of |interleaved| samples. The
// memory backing all of the input arrays and the output array is assumed to be
// suitably aligned.
template <typename Element>
void InterleaveAudioSamples(const uint8_t* const planes[],
int num_channels,
int num_samples,
uint8_t* interleaved) {
TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
// Note: This could be optimized with SIMD intrinsics for much better
// performance.
auto* dest = reinterpret_cast<Element*>(interleaved);
for (int ch = 0; ch < num_channels; ++ch) {
auto* const src = reinterpret_cast<const Element*>(planes[ch]);
for (int i = 0; i < num_samples; ++i) {
dest[i * num_channels] = src[i];
}
++dest;
}
}
} // namespace
SDLAudioPlayer::SDLAudioPlayer(ClockNowFunctionPtr now_function,
TaskRunner* task_runner,
Receiver* receiver,
AudioCodec codec,
std::function<void()> error_callback)
: SDLPlayerBase(now_function,
task_runner,
receiver,
CodecToString(codec),
std::move(error_callback),
kAudioMediaType) {}
SDLAudioPlayer::~SDLAudioPlayer() {
if (device_ > 0) {
SDL_CloseAudioDevice(device_);
}
}
ErrorOr<Clock::time_point> SDLAudioPlayer::RenderNextFrame(
const SDLPlayerBase::PresentableFrame& next_frame) {
TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
OSP_DCHECK(next_frame.decoded_frame);
const AVFrame& frame = *next_frame.decoded_frame;
pending_audio_spec_ = device_spec_;
pending_audio_spec_.freq = frame.sample_rate;
// Punt if the AVFrame's format is not compatible with those supported by SDL.
const auto frame_format = static_cast<AVSampleFormat>(frame.format);
pending_audio_spec_.format = GetSDLAudioFormat(frame_format);
if (pending_audio_spec_.format == kSDLAudioFormatUnknown) {
std::ostringstream error;
error << "SDL does not support AVSampleFormat " << frame_format;
return Error(Error::Code::kUnknownError, error.str());
}
// Punt if the number of channels is not supported by SDL.
constexpr int kSdlSupportedChannelCounts[] = {1, 2, 4, 6};
if (std::find(std::begin(kSdlSupportedChannelCounts),
std::end(kSdlSupportedChannelCounts),
frame.channels) == std::end(kSdlSupportedChannelCounts)) {
std::ostringstream error;
error << "SDL does not support " << frame.channels << " audio channels.";
return Error(Error::Code::kUnknownError, error.str());
}
pending_audio_spec_.channels = frame.channels;
// If |device_spec_| is different from what is required, re-compute the sample
// buffer size and the amount of time that represents. The |device_spec_| will
// be updated to match |pending_audio_spec_| later, in Present().
if (SDLAudioSpecsAreDifferent(device_spec_, pending_audio_spec_)) {
// Find the smallest power-of-two number of samples that represents at least
// 20ms of audio.
constexpr auto kMinBufferDuration = milliseconds(20);
constexpr auto kOneSecond = seconds(1);
const auto required_samples = static_cast<int>(
pending_audio_spec_.freq * kMinBufferDuration / kOneSecond);
OSP_DCHECK_GE(required_samples, 1);
pending_audio_spec_.samples = 1 << av_log2(required_samples);
if (pending_audio_spec_.samples < required_samples) {
pending_audio_spec_.samples *= 2;
}
approximate_lead_time_ =
(pending_audio_spec_.samples * Clock::to_duration(kOneSecond)) /
pending_audio_spec_.freq;
}
// If the decoded audio is in planar format, interleave it for SDL.
const int bytes_per_sample = av_get_bytes_per_sample(frame_format);
const int byte_count = frame.nb_samples * frame.channels * bytes_per_sample;
if (av_sample_fmt_is_planar(frame_format)) {
interleaved_audio_buffer_.resize(byte_count);
switch (bytes_per_sample) {
case 1:
InterleaveAudioSamples<uint8_t>(frame.data, frame.channels,
frame.nb_samples,
&interleaved_audio_buffer_[0]);
break;
case 2:
InterleaveAudioSamples<uint16_t>(frame.data, frame.channels,
frame.nb_samples,
&interleaved_audio_buffer_[0]);
break;
case 4:
InterleaveAudioSamples<uint32_t>(frame.data, frame.channels,
frame.nb_samples,
&interleaved_audio_buffer_[0]);
break;
default:
OSP_NOTREACHED();
break;
}
pending_audio_ = absl::Span<const uint8_t>(interleaved_audio_buffer_);
} else {
if (!interleaved_audio_buffer_.empty()) {
interleaved_audio_buffer_.clear();
interleaved_audio_buffer_.shrink_to_fit();
}
pending_audio_ = absl::Span<const uint8_t>(frame.data[0], byte_count);
}
// SDL provides no way to query the actual lead time before audio samples will
// be output by the sound hardware. The only advice seems to be a quick
// comment about "the intent is double buffered audio." Thus, schedule the
// "push" of this data to happen such that the audio will be playing out of
// the hardware at the intended moment in time.
return next_frame.presentation_time - approximate_lead_time_;
}
bool SDLAudioPlayer::RenderWhileIdle(const PresentableFrame* frame) {
// Do nothing. The SDL audio buffer will underrun and result in silence.
return false;
}
void SDLAudioPlayer::Present() {
TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
if (state() != kScheduledToPresent) {
// In all other states, just do nothing. The SDL audio buffer will underrun
// and result in silence.
return;
}
// Re-open audio device, if the audio format has changed.
if (SDLAudioSpecsAreDifferent(pending_audio_spec_, device_spec_)) {
if (device_ > 0) {
SDL_CloseAudioDevice(device_);
device_spec_ = SDL_AudioSpec{};
}
device_ = SDL_OpenAudioDevice(nullptr, // Pick default device.
0, // For playback, not recording.
&pending_audio_spec_, // Desired format.
&device_spec_, // [output] Obtained format.
0 // Disallow formats other than desired.
);
if (device_ <= 0) {
std::ostringstream error;
error << "SDL_OpenAudioDevice failed: " << SDL_GetError();
OnFatalError(error.str());
return;
}
OSP_DCHECK(!SDLAudioSpecsAreDifferent(pending_audio_spec_, device_spec_));
constexpr int kSdlResumePlaybackCommand = 0;
SDL_PauseAudioDevice(device_, kSdlResumePlaybackCommand);
}
SDL_QueueAudio(device_, pending_audio_.data(), pending_audio_.size());
}
// static
SDL_AudioFormat SDLAudioPlayer::GetSDLAudioFormat(AVSampleFormat format) {
switch (format) {
case AV_SAMPLE_FMT_U8P:
case AV_SAMPLE_FMT_U8:
return AUDIO_U8;
case AV_SAMPLE_FMT_S16P:
case AV_SAMPLE_FMT_S16:
return IsBigEndianArchitecture() ? AUDIO_S16MSB : AUDIO_S16LSB;
case AV_SAMPLE_FMT_S32P:
case AV_SAMPLE_FMT_S32:
return IsBigEndianArchitecture() ? AUDIO_S32MSB : AUDIO_S32LSB;
case AV_SAMPLE_FMT_FLTP:
case AV_SAMPLE_FMT_FLT:
return IsBigEndianArchitecture() ? AUDIO_F32MSB : AUDIO_F32LSB;
default:
// Either NONE, or the 64-bit formats are unsupported.
break;
}
return kSDLAudioFormatUnknown;
}
} // namespace cast
} // namespace openscreen