Index: services/media/audio/audio_track_impl.cc |
diff --git a/services/media/audio/audio_track_impl.cc b/services/media/audio/audio_track_impl.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f0a22635d35084ec53001372438ca6e8ed60cf26 |
--- /dev/null |
+++ b/services/media/audio/audio_track_impl.cc |
@@ -0,0 +1,274 @@ |
+// 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 |