// 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 #include #include #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 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(interleaved); for (int ch = 0; ch < num_channels; ++ch) { auto* const src = reinterpret_cast(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 error_callback) : SDLPlayerBase(now_function, task_runner, receiver, CodecToString(codec), std::move(error_callback), kAudioMediaType) {} SDLAudioPlayer::~SDLAudioPlayer() { if (device_ > 0) { SDL_CloseAudioDevice(device_); } } ErrorOr 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(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( 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(frame.data, frame.channels, frame.nb_samples, &interleaved_audio_buffer_[0]); break; case 2: InterleaveAudioSamples(frame.data, frame.channels, frame.nb_samples, &interleaved_audio_buffer_[0]); break; case 4: InterleaveAudioSamples(frame.data, frame.channels, frame.nb_samples, &interleaved_audio_buffer_[0]); break; default: OSP_NOTREACHED(); break; } pending_audio_ = absl::Span(interleaved_audio_buffer_); } else { if (!interleaved_audio_buffer_.empty()) { interleaved_audio_buffer_.clear(); interleaved_audio_buffer_.shrink_to_fit(); } pending_audio_ = absl::Span(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