blob: f0a22635d35084ec53001372438ca6e8ed60cf26 [file] [log] [blame]
// Copyright 2015 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 <algorithm>
#include <limits>
#include "base/logging.h"
#include "mojo/services/media/common/cpp/linear_transform.h"
#include "services/media/audio/audio_output_manager.h"
#include "services/media/audio/audio_server_impl.h"
#include "services/media/audio/audio_track_impl.h"
#include "services/media/audio/audio_track_to_output_link.h"
namespace mojo {
namespace media {
namespace audio {
constexpr size_t AudioTrackImpl::PTS_FRACTIONAL_BITS;
// TODO(johngro): If there is ever a better way to do this type of static-table
// initialization using mojom generated structs, we should switch to it.
static const struct {
LpcmSampleFormat sample_format;
uint8_t min_samples_per_frame;
uint8_t max_samples_per_frame;
uint32_t min_frames_per_second;
uint32_t max_frames_per_second;
} kSupportedLpcmTypeSets[] = {
{
.sample_format = LpcmSampleFormat::UNSIGNED_8,
.min_samples_per_frame = 1,
.max_samples_per_frame = 2,
.min_frames_per_second = 1000,
.max_frames_per_second = 48000,
},
{
.sample_format = LpcmSampleFormat::SIGNED_16,
.min_samples_per_frame = 1,
.max_samples_per_frame = 2,
.min_frames_per_second = 1000,
.max_frames_per_second = 48000,
},
};
AudioTrackImpl::AudioTrackImpl(InterfaceRequest<AudioTrack> iface,
AudioServerImpl* owner)
: owner_(owner),
binding_(this),
pipe_(this, owner) {
CHECK(nullptr != owner_);
binding_.Bind(iface.Pass());
}
AudioTrackImpl::~AudioTrackImpl() {
}
AudioTrackImplPtr AudioTrackImpl::Create(InterfaceRequest<AudioTrack> iface,
AudioServerImpl* owner) {
AudioTrackImplPtr ret(new AudioTrackImpl(iface.Pass(), owner));
ret->weak_this_ = ret;
return ret;
}
void AudioTrackImpl::Describe(const DescribeCallback& cbk) {
// Build a minimal descriptor
//
// TODO(johngro): one day, we need to make this description much more rich and
// fully describe our capabilities, based on things like what outputs are
// available, the class of hardware we are on, and what options we were
// compiled with.
//
// For now, it would be nice to just be able to have a static const tree of
// capabilities in this translational unit which we could use to construct our
// message, but the nature of the structures generated by the C++ bindings
// make this difficult. For now, we just create a trivial descriptor entierly
// by hand.
AudioTrackDescriptorPtr desc(AudioTrackDescriptor::New());
desc->supported_media_types =
Array<MediaTypeSetPtr>::New(arraysize(kSupportedLpcmTypeSets));
for (size_t i = 0; i < desc->supported_media_types.size(); ++i) {
const MediaTypeSetPtr& mts =
(desc->supported_media_types[i] = MediaTypeSet::New());
mts->scheme = MediaTypeScheme::LPCM;
mts->details = MediaTypeSetDetails::New();
const auto& s = kSupportedLpcmTypeSets[i];
LpcmMediaTypeSetDetailsPtr lpcm_detail = LpcmMediaTypeSetDetails::New();
lpcm_detail->sample_format = s.sample_format;
lpcm_detail->min_samples_per_frame = s.min_samples_per_frame;
lpcm_detail->max_samples_per_frame = s.max_samples_per_frame;
lpcm_detail->min_frames_per_second = s.min_frames_per_second;
lpcm_detail->max_frames_per_second = s.max_frames_per_second;
mts->details->set_lpcm(lpcm_detail.Pass());
}
cbk.Run(desc.Pass());
}
void AudioTrackImpl::Configure(AudioTrackConfigurationPtr configuration,
InterfaceRequest<MediaPipe> req,
const ConfigureCallback& cbk) {
// Are we already configured?
if (pipe_.IsInitialized()) {
cbk.Run(MediaResult::BAD_STATE);
return;
}
// Check the requested configuration.
if ((configuration->media_type->scheme != MediaTypeScheme::LPCM) ||
(!configuration->media_type->details->is_lpcm())) {
cbk.Run(MediaResult::UNSUPPORTED_CONFIG);
return;
}
// Search our supported configuration sets to find one compatible with this
// request.
auto& cfg = configuration->media_type->details->get_lpcm();
size_t i;
for (i = 0; i < arraysize(kSupportedLpcmTypeSets); ++i) {
const auto& cfg_set = kSupportedLpcmTypeSets[i];
if ((cfg->sample_format == cfg_set.sample_format) &&
(cfg->samples_per_frame >= cfg_set.min_samples_per_frame) &&
(cfg->samples_per_frame <= cfg_set.max_samples_per_frame) &&
(cfg->frames_per_second >= cfg_set.min_frames_per_second) &&
(cfg->frames_per_second <= cfg_set.max_frames_per_second)) {
break;
}
}
if (i >= arraysize(kSupportedLpcmTypeSets)) {
cbk.Run(MediaResult::UNSUPPORTED_CONFIG);
return;
}
// Sanity check the ratio which relates audio frames to media time.
int32_t numerator = static_cast<int32_t>(configuration->audio_frame_ratio);
uint32_t denominator = static_cast<int32_t>(configuration->media_time_ratio);
if ((numerator < 1) || (denominator < 1)) {
cbk.Run(MediaResult::INVALID_ARGUMENT);
return;
}
// Figure out the rate we need to scale by in order to produce our fixed
// point timestamps.
LinearTransform::Ratio frac_scale(1 << PTS_FRACTIONAL_BITS, 1);
LinearTransform::Ratio frame_scale(LinearTransform::Ratio(numerator,
denominator));
bool no_loss = LinearTransform::Ratio::Compose(frac_scale,
frame_scale,
&frame_to_media_ratio_);
if (!no_loss) {
cbk.Run(MediaResult::INVALID_ARGUMENT);
return;
}
// Figure out how many bytes we need to hold the requested number of nSec of
// audio.
switch (cfg->sample_format) {
case LpcmSampleFormat::UNSIGNED_8:
bytes_per_frame_ = 1;
break;
case LpcmSampleFormat::SIGNED_16:
bytes_per_frame_ = 2;
break;
case LpcmSampleFormat::SIGNED_24_IN_32:
bytes_per_frame_ = 4;
break;
default:
DCHECK(false);
bytes_per_frame_ = 2;
break;
}
bytes_per_frame_ *= cfg->samples_per_frame;
// Overflow trying to convert from frames to bytes?
uint64_t requested_frames = configuration->max_frames;
if (requested_frames >
(std::numeric_limits<size_t>::max() / bytes_per_frame_)) {
cbk.Run(MediaResult::INSUFFICIENT_RESOURCES);
return;
}
size_t requested_bytes = (requested_frames * bytes_per_frame_);
// Attempt to initialize our shared buffer and bind it to our interface
// request.
if (pipe_.Init(req.Pass(), requested_bytes) != MOJO_RESULT_OK) {
cbk.Run(MediaResult::INSUFFICIENT_RESOURCES);
return;
}
// Stash our configuration.
format_ = cfg.Pass();
// Have the audio output manager initialize our set of outputs. Note; there
// is currently no need for a lock here. Methods called from our user-facing
// interfaces are seriailzed by nature of the mojo framework, and none of the
// output manager's threads should ever need to manipulate the set. Cleanup
// of outputs which have gone away is currently handled in a lazy fashion when
// the track fails to promote its weak reference during an operation involving
// its outputs.
//
// TODO(johngro): someday, we will need to deal with recalculating properties
// which depend on a track's current set of outputs (for example, the minimum
// latency). This will probably be done using a dirty flag in the track
// implementations, and scheduling a job to recalculate the properties for the
// dirty tracks and notify the users as appropriate.
// If we cannot promote our own weak pointer, something is seriously wrong.
AudioTrackImplPtr strong_this(weak_this_.lock());
DCHECK(strong_this);
DCHECK(owner_);
owner_->GetOutputManager().SelectOutputsForTrack(strong_this);
// Done
cbk.Run(MediaResult::OK);
}
void AudioTrackImpl::GetRateControl(InterfaceRequest<RateControl> req,
const GetRateControlCallback& cbk) {
cbk.Run(rate_control_.Bind(req.Pass()));
}
void AudioTrackImpl::AddOutput(AudioTrackToOutputLinkPtr link) {
// TODO(johngro): assert that we are on the main message loop thread.
DCHECK(link);
auto res = outputs_.emplace(link);
DCHECK(res.second);
}
void AudioTrackImpl::RemoveOutput(AudioTrackToOutputLinkPtr link) {
// TODO(johngro): assert that we are on the main message loop thread.
DCHECK(link);
auto iter = outputs_.find(link);
if (iter != outputs_.end()) {
outputs_.erase(iter);
} else {
// TODO(johngro): that's odd. I can't think of a reason why we we should
// not be able to find this link in our set of outputs... should we log
// something about this?
DCHECK(false);
}
}
void AudioTrackImpl::OnPacketReceived(AudioPipe::AudioPacketRefPtr packet) {
DCHECK(packet);
for (const auto& output : outputs_) {
DCHECK(output);
output->PushToPendingQueue(packet);
}
}
void AudioTrackImpl::OnFlushRequested(const MediaPipe::FlushCallback& cbk) {
for (const auto& output : outputs_) {
DCHECK(output);
output->FlushPendingQueue();
}
cbk.Run(MediaResult::OK);
}
} // namespace audio
} // namespace media
} // namespace mojo