blob: cd35b97713971a146fc014e1b275d535519fe9d6 [file] [log] [blame]
/*
* 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