| // 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 |