You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
235 lines
8.5 KiB
235 lines
8.5 KiB
// 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
|