| // Copyright 2015 Google Inc. 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 "cobalt/audio/audio_device.h" |
| |
| #include "base/debug/trace_event.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "cobalt/audio/audio_helpers.h" |
| #if defined(OS_STARBOARD) |
| #include "starboard/audio_sink.h" |
| #include "starboard/configuration.h" |
| #endif // defined(OS_STARBOARD) |
| #include "media/audio/audio_parameters.h" |
| #include "media/audio/shell_audio_streamer.h" |
| #include "media/base/audio_bus.h" |
| |
| namespace cobalt { |
| namespace audio { |
| |
| using ::media::AudioBus; |
| using ::media::ShellAudioBus; |
| |
| namespace { |
| const int kRenderBufferSizeFrames = 1024; |
| const int kFramesPerChannel = kRenderBufferSizeFrames * 4; |
| const int kStandardOutputSampleRate = 48000; |
| } // namespace |
| |
| #if defined(OS_STARBOARD) |
| #if SB_CAN(MEDIA_USE_STARBOARD_PIPELINE) |
| #define SB_USE_SB_AUDIO_SINK 1 |
| #endif // SB_CAN(MEDIA_USE_STARBOARD_PIPELINE) |
| #endif // defined(OS_STARBOARD) |
| |
| #if defined(SB_USE_SB_AUDIO_SINK) |
| |
| class AudioDevice::Impl { |
| public: |
| Impl(int number_of_channels, RenderCallback* callback); |
| ~Impl(); |
| |
| private: |
| static void UpdateSourceStatusFunc(int* frames_in_buffer, |
| int* offset_in_frames, bool* is_playing, |
| bool* is_eos_reached, void* context); |
| static void ConsumeFramesFunc(int frames_consumed, void* context); |
| |
| void UpdateSourceStatus(int* frames_in_buffer, int* offset_in_frames, |
| bool* is_playing, bool* is_eos_reached); |
| void ConsumeFrames(int frames_consumed); |
| |
| void FillOutputAudioBus(); |
| |
| template <typename InputType, typename OutputType> |
| inline void FillOutputAudioBusForType(); |
| |
| int number_of_channels_; |
| SbMediaAudioSampleType output_sample_type_; |
| RenderCallback* render_callback_; |
| |
| // The |render_callback_| returns audio data in planar form. So we read it |
| // into |input_audio_bus_| and convert it into interleaved form and store in |
| // |output_frame_buffer_|. |
| ShellAudioBus input_audio_bus_; |
| |
| scoped_array<uint8> output_frame_buffer_; |
| |
| void* frame_buffers_[1]; |
| int64 frames_rendered_; // Frames retrieved from |render_callback_|. |
| int64 frames_consumed_; // Accumulated frames consumed reported by the sink. |
| |
| bool was_silence_last_update_; |
| |
| SbAudioSink audio_sink_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Impl); |
| }; |
| |
| // AudioDevice::Impl. |
| AudioDevice::Impl::Impl(int number_of_channels, RenderCallback* callback) |
| : number_of_channels_(number_of_channels), |
| output_sample_type_(GetPreferredOutputStarboardSampleType()), |
| render_callback_(callback), |
| input_audio_bus_(static_cast<size_t>(number_of_channels), |
| static_cast<size_t>(kRenderBufferSizeFrames), |
| GetPreferredOutputSampleType(), ShellAudioBus::kPlanar), |
| output_frame_buffer_( |
| new uint8[kFramesPerChannel * number_of_channels_ * |
| GetStarboardSampleTypeSize(output_sample_type_)]), |
| frames_rendered_(0), |
| frames_consumed_(0), |
| was_silence_last_update_(false), |
| audio_sink_(kSbAudioSinkInvalid) { |
| DCHECK(number_of_channels_ == 1 || number_of_channels_ == 2) |
| << "Invalid number of channels: " << number_of_channels_; |
| DCHECK(render_callback_); |
| DCHECK(SbAudioSinkIsAudioFrameStorageTypeSupported( |
| kSbMediaAudioFrameStorageTypeInterleaved)) |
| << "Only interleaved frame storage is supported."; |
| DCHECK(SbAudioSinkIsAudioSampleTypeSupported(output_sample_type_)) |
| << "Output sample type " << output_sample_type_ << " is not supported."; |
| |
| frame_buffers_[0] = output_frame_buffer_.get(); |
| audio_sink_ = SbAudioSinkCreate( |
| number_of_channels_, kStandardOutputSampleRate, output_sample_type_, |
| kSbMediaAudioFrameStorageTypeInterleaved, frame_buffers_, |
| kFramesPerChannel, &AudioDevice::Impl::UpdateSourceStatusFunc, |
| &AudioDevice::Impl::ConsumeFramesFunc, this); |
| DCHECK(SbAudioSinkIsValid(audio_sink_)); |
| } |
| |
| AudioDevice::Impl::~Impl() { |
| if (SbAudioSinkIsValid(audio_sink_)) { |
| SbAudioSinkDestroy(audio_sink_); |
| } |
| } |
| |
| // static |
| void AudioDevice::Impl::UpdateSourceStatusFunc(int* frames_in_buffer, |
| int* offset_in_frames, |
| bool* is_playing, |
| bool* is_eos_reached, |
| void* context) { |
| AudioDevice::Impl* impl = reinterpret_cast<AudioDevice::Impl*>(context); |
| DCHECK(impl); |
| DCHECK(frames_in_buffer); |
| DCHECK(offset_in_frames); |
| DCHECK(is_playing); |
| DCHECK(is_eos_reached); |
| |
| impl->UpdateSourceStatus(frames_in_buffer, offset_in_frames, is_playing, |
| is_eos_reached); |
| } |
| |
| // static |
| void AudioDevice::Impl::ConsumeFramesFunc(int frames_consumed, void* context) { |
| AudioDevice::Impl* impl = reinterpret_cast<AudioDevice::Impl*>(context); |
| DCHECK(impl); |
| |
| impl->ConsumeFrames(frames_consumed); |
| } |
| |
| void AudioDevice::Impl::UpdateSourceStatus(int* frames_in_buffer, |
| int* offset_in_frames, |
| bool* is_playing, |
| bool* is_eos_reached) { |
| TRACE_EVENT0("cobalt::audio", "AudioDevice::Impl::UpdateSourceStatus()"); |
| *is_playing = true; |
| *is_eos_reached = false; |
| |
| // Assert that we never consume more than we've rendered. |
| DCHECK_GE(frames_rendered_, frames_consumed_); |
| *frames_in_buffer = static_cast<int>(frames_rendered_ - frames_consumed_); |
| |
| if ((kFramesPerChannel - *frames_in_buffer) >= kRenderBufferSizeFrames) { |
| bool silence = false; |
| |
| // If there was silence last time we were called, then the buffer has |
| // already been zeroed out and we don't need to do it again. |
| if (!was_silence_last_update_) { |
| input_audio_bus_.ZeroAllFrames(); |
| } |
| |
| // Fill our temporary buffer with planar PCM float samples. |
| render_callback_->FillAudioBus(&input_audio_bus_, &silence); |
| |
| if (!silence) { |
| FillOutputAudioBus(); |
| |
| frames_rendered_ += kRenderBufferSizeFrames; |
| *frames_in_buffer += kRenderBufferSizeFrames; |
| } |
| |
| was_silence_last_update_ = silence; |
| } |
| |
| *offset_in_frames = frames_consumed_ % kFramesPerChannel; |
| *is_playing = (frames_rendered_ != frames_consumed_); |
| } |
| |
| void AudioDevice::Impl::ConsumeFrames(int frames_consumed) { |
| frames_consumed_ += frames_consumed; |
| } |
| |
| template <typename InputType, typename OutputType> |
| inline void AudioDevice::Impl::FillOutputAudioBusForType() { |
| // Determine the offset into the audio bus that represents the tail of |
| // buffered data. |
| uint64 channel_offset = frames_rendered_ % kFramesPerChannel; |
| |
| OutputType* output_buffer = |
| reinterpret_cast<OutputType*>(output_frame_buffer_.get()); |
| output_buffer += channel_offset * number_of_channels_; |
| for (size_t frame = 0; frame < kRenderBufferSizeFrames; ++frame) { |
| for (size_t channel = 0; channel < input_audio_bus_.channels(); ++channel) { |
| *output_buffer = ConvertSample<InputType, OutputType>( |
| input_audio_bus_ |
| .GetSampleForType<InputType, media::ShellAudioBus::kPlanar>( |
| channel, frame)); |
| ++output_buffer; |
| } |
| } |
| } |
| |
| void AudioDevice::Impl::FillOutputAudioBus() { |
| TRACE_EVENT0("cobalt::audio", "AudioDevice::Impl::FillOutputAudioBus()"); |
| |
| const bool is_input_int16 = |
| input_audio_bus_.sample_type() == media::ShellAudioBus::kInt16; |
| const bool is_output_int16 = |
| output_sample_type_ == kSbMediaAudioSampleTypeInt16; |
| |
| if (is_input_int16 && is_output_int16) { |
| FillOutputAudioBusForType<int16, int16>(); |
| } else if (!is_input_int16 && is_output_int16) { |
| FillOutputAudioBusForType<float, int16>(); |
| } else if (is_input_int16 && !is_output_int16) { |
| FillOutputAudioBusForType<int16, float>(); |
| } else if (!is_input_int16 && !is_output_int16) { |
| FillOutputAudioBusForType<float, float>(); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| #else // defined(SB_USE_SB_AUDIO_SINK) |
| |
| class AudioDevice::Impl : public ::media::ShellAudioStream { |
| public: |
| typedef ::media::AudioBus AudioBus; |
| typedef ::media::AudioParameters AudioParameters; |
| |
| Impl(int32 number_of_channels, RenderCallback* callback); |
| virtual ~Impl(); |
| |
| // ShellAudioStream implementation. |
| bool PauseRequested() const OVERRIDE; |
| bool PullFrames(uint32* offset_in_frame, uint32* total_frames) OVERRIDE; |
| void ConsumeFrames(uint32 frame_played) OVERRIDE; |
| const AudioParameters& GetAudioParameters() const OVERRIDE; |
| AudioBus* GetAudioBus() OVERRIDE; |
| |
| private: |
| typedef ::media::ShellAudioBus ShellAudioBus; |
| |
| int GetAudioHardwareSampleRate(); |
| |
| void FillOutputAudioBus(); |
| |
| AudioParameters audio_parameters_; |
| scoped_ptr<AudioBus> output_audio_bus_; |
| |
| uint64 rendered_frame_cursor_; |
| uint64 buffered_frame_cursor_; |
| bool needs_data_; |
| |
| // Buffer the audio data which is pulled from upper level. |
| ShellAudioBus audio_bus_; |
| |
| RenderCallback* render_callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Impl); |
| }; |
| |
| // AudioDevice::Impl. |
| AudioDevice::Impl::Impl(int32 number_of_channels, RenderCallback* callback) |
| : rendered_frame_cursor_(0), |
| buffered_frame_cursor_(0), |
| needs_data_(true), |
| audio_bus_(static_cast<size_t>(number_of_channels), |
| static_cast<size_t>(kRenderBufferSizeFrames), |
| ShellAudioBus::kFloat32, ShellAudioBus::kPlanar), |
| render_callback_(callback) { |
| TRACE_EVENT0("cobalt::audio", "AudioDevice::Impl::Impl()"); |
| |
| DCHECK_GT(number_of_channels, 0); |
| DCHECK(media::ShellAudioStreamer::Instance()->GetConfig().interleaved()) |
| << "Planar audio is not supported."; |
| |
| int bytes_per_sample = static_cast<int>( |
| media::ShellAudioStreamer::Instance()->GetConfig().bytes_per_sample()); |
| |
| DCHECK_EQ(bytes_per_sample, sizeof(float)) |
| << bytes_per_sample << " bytes per sample is not supported."; |
| |
| media::ChannelLayout channel_layout = number_of_channels == 1 |
| ? media::CHANNEL_LAYOUT_MONO |
| : media::CHANNEL_LAYOUT_STEREO; |
| |
| audio_parameters_ = |
| media::AudioParameters(media::AudioParameters::AUDIO_PCM_LINEAR, |
| channel_layout, GetAudioHardwareSampleRate(), |
| bytes_per_sample * 8, kRenderBufferSizeFrames); |
| |
| // Create 1 channel audio bus since we only support interleaved. |
| output_audio_bus_ = |
| AudioBus::Create(1, kFramesPerChannel * number_of_channels); |
| |
| audio_bus_.ZeroAllFrames(); |
| |
| media::ShellAudioStreamer::Instance()->AddStream(this); |
| } |
| |
| AudioDevice::Impl::~Impl() { |
| media::ShellAudioStreamer::Instance()->RemoveStream(this); |
| } |
| |
| bool AudioDevice::Impl::PauseRequested() const { return needs_data_; } |
| |
| bool AudioDevice::Impl::PullFrames(uint32* offset_in_frame, |
| uint32* total_frames) { |
| TRACE_EVENT0("cobalt::audio", "AudioDevice::Impl::PullFrames()"); |
| |
| // In case offset_in_frame or total_frames is NULL. |
| uint32 dummy_offset_in_frame; |
| uint32 dummy_total_frames; |
| if (!offset_in_frame) { |
| offset_in_frame = &dummy_offset_in_frame; |
| } |
| if (!total_frames) { |
| total_frames = &dummy_total_frames; |
| } |
| |
| // Assert that we never render more than has been buffered. |
| DCHECK_GE(buffered_frame_cursor_, rendered_frame_cursor_); |
| *total_frames = |
| static_cast<uint32>(buffered_frame_cursor_ - rendered_frame_cursor_); |
| |
| if ((kFramesPerChannel - *total_frames) >= kRenderBufferSizeFrames) { |
| // Fill our temporary buffer with PCM float samples. |
| bool silence = false; |
| render_callback_->FillAudioBus(&audio_bus_, &silence); |
| |
| if (!silence) { |
| FillOutputAudioBus(); |
| |
| buffered_frame_cursor_ += kRenderBufferSizeFrames; |
| *total_frames += kRenderBufferSizeFrames; |
| } |
| } |
| |
| needs_data_ = *total_frames < kRenderBufferSizeFrames; |
| *offset_in_frame = rendered_frame_cursor_ % kFramesPerChannel; |
| return !PauseRequested(); |
| } |
| |
| void AudioDevice::Impl::ConsumeFrames(uint32 frame_played) { |
| // Increment number of frames rendered by the hardware. |
| rendered_frame_cursor_ += frame_played; |
| } |
| |
| const AudioDevice::Impl::AudioParameters& |
| AudioDevice::Impl::GetAudioParameters() const { |
| return audio_parameters_; |
| } |
| |
| AudioDevice::Impl::AudioBus* AudioDevice::Impl::GetAudioBus() { |
| return output_audio_bus_.get(); |
| } |
| |
| int AudioDevice::Impl::GetAudioHardwareSampleRate() { |
| int native_output_sample_rate = |
| static_cast<int>(media::ShellAudioStreamer::Instance() |
| ->GetConfig() |
| .native_output_sample_rate()); |
| if (native_output_sample_rate != |
| media::ShellAudioStreamer::Config::kInvalidSampleRate) { |
| return native_output_sample_rate; |
| } |
| return kStandardOutputSampleRate; |
| } |
| |
| void AudioDevice::Impl::FillOutputAudioBus() { |
| TRACE_EVENT0("cobalt::audio", "AudioDevice::Impl::FillOutputAudioBus()"); |
| // Determine the offset into the audio bus that represents the tail of |
| // buffered data. |
| uint64 channel_offset = buffered_frame_cursor_ % kFramesPerChannel; |
| |
| float* output_buffer = output_audio_bus_->channel(0); |
| output_buffer += channel_offset * audio_parameters_.channels(); |
| |
| for (size_t i = 0; i < kRenderBufferSizeFrames; ++i) { |
| for (size_t c = 0; c < audio_bus_.channels(); ++c) { |
| *output_buffer = audio_bus_.GetFloat32Sample(c, i); |
| ++output_buffer; |
| } |
| } |
| |
| // Clear the data in audio bus. |
| audio_bus_.ZeroAllFrames(); |
| } |
| |
| #endif // defined(SB_USE_SB_AUDIO_SINK) |
| |
| // AudioDevice. |
| AudioDevice::AudioDevice(int32 number_of_channels, RenderCallback* callback) |
| : impl_(new Impl(number_of_channels, callback)) {} |
| |
| AudioDevice::~AudioDevice() {} |
| |
| } // namespace audio |
| } // namespace cobalt |