| /* |
| * Copyright 2013 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 "media/audio/shell_audio_streamer_linux.h" |
| |
| #include "base/logging.h" |
| #include "lb_platform.h" |
| #include "media/audio/audio_parameters.h" |
| #include "media/audio/shell_pulse_audio.h" |
| #include "media/base/audio_bus.h" |
| #include "media/mp4/aac.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| ShellAudioStreamerLinux* instance = NULL; |
| |
| } // namespace |
| |
| class PulseAudioHost : public ShellPulseAudioStream::Host { |
| public: |
| PulseAudioHost(ShellPulseAudioContext* pulse_audio_context, |
| ShellAudioStream* stream, |
| int rate, |
| int channels); |
| ~PulseAudioHost(); |
| virtual void RequestFrame(size_t length, WriteFunc write) OVERRIDE; |
| |
| private: |
| enum StreamState { |
| STATE_INVALID, |
| STATE_PAUSED, // Voice is paused, will play when unpaused |
| STATE_RUNNING, // Voice is playing, reading new data when possible |
| }; |
| |
| ShellPulseAudioContext* pulse_audio_context_; |
| int channels_; |
| uint32 played_frames_; // frames played by the audio driver |
| uint32 written_frames_; // frames written to the audio driver |
| StreamState state_; |
| ShellAudioStream* lb_audio_stream_; |
| ShellPulseAudioStream* pulse_audio_stream_; |
| }; |
| |
| ShellAudioStreamer::Config ShellAudioStreamerLinux::GetConfig() const { |
| const uint32 initial_rebuffering_frames_per_channel = |
| mp4::AAC::kFramesPerAccessUnit * 32; |
| const uint32 sink_buffer_size_in_frames_per_channel = |
| initial_rebuffering_frames_per_channel * 8; |
| const uint32 max_hardware_channels = 2; |
| |
| return Config(Config::INTERLEAVED, initial_rebuffering_frames_per_channel, |
| sink_buffer_size_in_frames_per_channel, max_hardware_channels, |
| sizeof(float) /* bytes_per_sample */); |
| } |
| |
| bool ShellAudioStreamerLinux::AddStream(ShellAudioStream* stream) { |
| base::AutoLock lock(streams_lock_); |
| |
| if (pulse_audio_context_ == NULL) { |
| pulse_audio_context_.reset(new ShellPulseAudioContext()); |
| bool result = pulse_audio_context_->Initialize(); |
| if (!result) { |
| pulse_audio_context_.reset(); |
| DLOG(WARNING) << "Failed to initialize pulse audio."; |
| return false; |
| } |
| } |
| |
| // other basic checks, it is assumed that the decoder or renderer algorithm |
| // will have rejected invalid configurations before creating a sink, so |
| // here they are asserts instead of run-time errors |
| const AudioParameters& params = stream->GetAudioParameters(); |
| DCHECK(params.channels() == 1 || params.channels() == 2); |
| DCHECK_EQ(params.bits_per_sample(), 32); |
| |
| const AudioParameters& audio_parameters = stream->GetAudioParameters(); |
| const int sample_rate = audio_parameters.sample_rate(); |
| |
| streams_[stream] = new PulseAudioHost(pulse_audio_context_.get(), stream, |
| sample_rate, params.channels()); |
| |
| return true; |
| } |
| |
| void ShellAudioStreamerLinux::RemoveStream(ShellAudioStream* stream) { |
| base::AutoLock lock(streams_lock_); |
| |
| StreamMap::iterator it = streams_.find(stream); |
| if (it == streams_.end()) |
| return; |
| delete it->second; |
| streams_.erase(it); |
| |
| if (streams_.empty()) { |
| pulse_audio_context_.reset(); |
| } |
| } |
| |
| bool ShellAudioStreamerLinux::HasStream(ShellAudioStream* stream) const { |
| base::AutoLock lock(streams_lock_); |
| return streams_.find(stream) != streams_.end(); |
| } |
| |
| bool ShellAudioStreamerLinux::SetVolume(ShellAudioStream* stream, |
| double volume) { |
| if (volume != 1.0) { |
| NOTIMPLEMENTED(); |
| } |
| return volume != 1.0; |
| } |
| |
| ShellAudioStreamerLinux::ShellAudioStreamerLinux() |
| : streams_value_deleter_(&streams_) { |
| instance = this; |
| } |
| |
| ShellAudioStreamerLinux::~ShellAudioStreamerLinux() { |
| DCHECK(streams_.empty()); |
| instance = NULL; |
| } |
| |
| PulseAudioHost::PulseAudioHost(ShellPulseAudioContext* pulse_audio_context, |
| ShellAudioStream* stream, |
| int rate, |
| int channels) |
| : channels_(channels), |
| pulse_audio_context_(pulse_audio_context), |
| played_frames_(0), |
| written_frames_(0), |
| state_(STATE_PAUSED), |
| lb_audio_stream_(stream) { |
| pulse_audio_stream_ = pulse_audio_context->CreateStream(this, rate, channels); |
| } |
| |
| PulseAudioHost::~PulseAudioHost() { |
| if (pulse_audio_stream_) { |
| pulse_audio_context_->DestroyStream(pulse_audio_stream_); |
| } |
| } |
| |
| void PulseAudioHost::RequestFrame(size_t length, WriteFunc write) { |
| uint64 time_played = pulse_audio_stream_->GetPlaybackCursorInMicroSeconds(); |
| int sample_rate = lb_audio_stream_->GetAudioParameters().sample_rate(); |
| uint64 frames_played = time_played * sample_rate / 1000000; |
| uint32 frame_consumed = 0; |
| uint32 frame_pulled; |
| |
| if (frames_played > written_frames_) |
| frames_played = written_frames_; |
| if (frames_played > played_frames_) |
| frame_consumed = frames_played - played_frames_; |
| played_frames_ += frame_consumed; |
| |
| // Our samples are in floats. |
| const int kBytesPerFrame = sizeof(float) * channels_; |
| DCHECK_EQ(length % kBytesPerFrame, 0); |
| length /= kBytesPerFrame; |
| const AudioBus* audio_bus = lb_audio_stream_->GetAudioBus(); |
| |
| lb_audio_stream_->ConsumeFrames(frame_consumed); |
| lb_audio_stream_->PullFrames(NULL, &frame_pulled); |
| |
| if (played_frames_ + frame_pulled > written_frames_ && length) { |
| frame_pulled = played_frames_ + frame_pulled - written_frames_; |
| frame_pulled = std::min<size_t>(frame_pulled, length); |
| |
| uint32 frames = audio_bus->frames() / channels_; |
| uint32 frame_offset = written_frames_ % frames; |
| |
| uint32 frame_to_write = |
| std::min<size_t>(frame_pulled, frames - frame_offset); |
| const float* buffer = audio_bus->channel(0) + frame_offset * channels_; |
| write.Run(reinterpret_cast<const uint8*>(buffer), |
| frame_to_write * kBytesPerFrame); |
| written_frames_ += frame_to_write; |
| } |
| |
| switch (state_) { |
| case STATE_PAUSED: |
| if (!lb_audio_stream_->PauseRequested()) { |
| pulse_audio_stream_->Play(); |
| state_ = STATE_RUNNING; |
| } |
| break; |
| case STATE_RUNNING: |
| if (lb_audio_stream_->PauseRequested()) { |
| pulse_audio_stream_->Pause(); |
| state_ = STATE_PAUSED; |
| } |
| break; |
| case STATE_INVALID: |
| break; |
| } |
| } |
| |
| void ShellAudioStreamer::Initialize() { |
| CHECK(!instance); |
| new ShellAudioStreamerLinux(); |
| } |
| |
| void ShellAudioStreamer::Terminate() { |
| CHECK(instance); |
| delete instance; |
| instance = NULL; |
| } |
| |
| ShellAudioStreamer* ShellAudioStreamer::Instance() { |
| CHECK(instance); |
| return instance; |
| } |
| |
| } // namespace media |