OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include <algorithm> |
| 6 #include <limits> |
| 7 |
| 8 #include "base/logging.h" |
| 9 #include "mojo/services/media/common/cpp/linear_transform.h" |
| 10 #include "services/media/audio/audio_output_manager.h" |
| 11 #include "services/media/audio/audio_server_impl.h" |
| 12 #include "services/media/audio/audio_track_impl.h" |
| 13 #include "services/media/audio/audio_track_to_output_link.h" |
| 14 |
| 15 namespace mojo { |
| 16 namespace media { |
| 17 namespace audio { |
| 18 |
| 19 constexpr size_t AudioTrackImpl::PTS_FRACTIONAL_BITS; |
| 20 |
| 21 // TODO(johngro): If there is ever a better way to do this type of static-table |
| 22 // initialization using mojom generated structs, we should switch to it. |
| 23 static const struct { |
| 24 LpcmSampleFormat sample_format; |
| 25 uint8_t min_samples_per_frame; |
| 26 uint8_t max_samples_per_frame; |
| 27 uint32_t min_frames_per_second; |
| 28 uint32_t max_frames_per_second; |
| 29 } kSupportedLpcmTypeSets[] = { |
| 30 { |
| 31 .sample_format = LpcmSampleFormat::UNSIGNED_8, |
| 32 .min_samples_per_frame = 1, |
| 33 .max_samples_per_frame = 2, |
| 34 .min_frames_per_second = 1000, |
| 35 .max_frames_per_second = 48000, |
| 36 }, |
| 37 { |
| 38 .sample_format = LpcmSampleFormat::SIGNED_16, |
| 39 .min_samples_per_frame = 1, |
| 40 .max_samples_per_frame = 2, |
| 41 .min_frames_per_second = 1000, |
| 42 .max_frames_per_second = 48000, |
| 43 }, |
| 44 }; |
| 45 |
| 46 AudioTrackImpl::AudioTrackImpl(InterfaceRequest<AudioTrack> iface, |
| 47 AudioServerImpl* owner) |
| 48 : owner_(owner), |
| 49 binding_(this), |
| 50 pipe_(this, owner) { |
| 51 CHECK(nullptr != owner_); |
| 52 binding_.Bind(iface.Pass()); |
| 53 } |
| 54 |
| 55 AudioTrackImpl::~AudioTrackImpl() { |
| 56 } |
| 57 |
| 58 AudioTrackImplPtr AudioTrackImpl::Create(InterfaceRequest<AudioTrack> iface, |
| 59 AudioServerImpl* owner) { |
| 60 AudioTrackImplPtr ret(new AudioTrackImpl(iface.Pass(), owner)); |
| 61 ret->weak_this_ = ret; |
| 62 return ret; |
| 63 } |
| 64 |
| 65 void AudioTrackImpl::Describe(const DescribeCallback& cbk) { |
| 66 // Build a minimal descriptor |
| 67 // |
| 68 // TODO(johngro): one day, we need to make this description much more rich and |
| 69 // fully describe our capabilities, based on things like what outputs are |
| 70 // available, the class of hardware we are on, and what options we were |
| 71 // compiled with. |
| 72 // |
| 73 // For now, it would be nice to just be able to have a static const tree of |
| 74 // capabilities in this translational unit which we could use to construct our |
| 75 // message, but the nature of the structures generated by the C++ bindings |
| 76 // make this difficult. For now, we just create a trivial descriptor entierly |
| 77 // by hand. |
| 78 AudioTrackDescriptorPtr desc(AudioTrackDescriptor::New()); |
| 79 |
| 80 desc->supported_media_types = |
| 81 Array<MediaTypeSetPtr>::New(arraysize(kSupportedLpcmTypeSets)); |
| 82 |
| 83 for (size_t i = 0; i < desc->supported_media_types.size(); ++i) { |
| 84 const MediaTypeSetPtr& mts = |
| 85 (desc->supported_media_types[i] = MediaTypeSet::New()); |
| 86 |
| 87 mts->scheme = MediaTypeScheme::LPCM; |
| 88 mts->details = MediaTypeSetDetails::New(); |
| 89 |
| 90 const auto& s = kSupportedLpcmTypeSets[i]; |
| 91 LpcmMediaTypeSetDetailsPtr lpcm_detail = LpcmMediaTypeSetDetails::New(); |
| 92 |
| 93 lpcm_detail->sample_format = s.sample_format; |
| 94 lpcm_detail->min_samples_per_frame = s.min_samples_per_frame; |
| 95 lpcm_detail->max_samples_per_frame = s.max_samples_per_frame; |
| 96 lpcm_detail->min_frames_per_second = s.min_frames_per_second; |
| 97 lpcm_detail->max_frames_per_second = s.max_frames_per_second; |
| 98 mts->details->set_lpcm(lpcm_detail.Pass()); |
| 99 } |
| 100 |
| 101 cbk.Run(desc.Pass()); |
| 102 } |
| 103 |
| 104 void AudioTrackImpl::Configure(AudioTrackConfigurationPtr configuration, |
| 105 InterfaceRequest<MediaPipe> req, |
| 106 const ConfigureCallback& cbk) { |
| 107 // Are we already configured? |
| 108 if (pipe_.IsInitialized()) { |
| 109 cbk.Run(MediaResult::BAD_STATE); |
| 110 return; |
| 111 } |
| 112 |
| 113 // Check the requested configuration. |
| 114 if ((configuration->media_type->scheme != MediaTypeScheme::LPCM) || |
| 115 (!configuration->media_type->details->is_lpcm())) { |
| 116 cbk.Run(MediaResult::UNSUPPORTED_CONFIG); |
| 117 return; |
| 118 } |
| 119 |
| 120 // Search our supported configuration sets to find one compatible with this |
| 121 // request. |
| 122 auto& cfg = configuration->media_type->details->get_lpcm(); |
| 123 size_t i; |
| 124 for (i = 0; i < arraysize(kSupportedLpcmTypeSets); ++i) { |
| 125 const auto& cfg_set = kSupportedLpcmTypeSets[i]; |
| 126 |
| 127 if ((cfg->sample_format == cfg_set.sample_format) && |
| 128 (cfg->samples_per_frame >= cfg_set.min_samples_per_frame) && |
| 129 (cfg->samples_per_frame <= cfg_set.max_samples_per_frame) && |
| 130 (cfg->frames_per_second >= cfg_set.min_frames_per_second) && |
| 131 (cfg->frames_per_second <= cfg_set.max_frames_per_second)) { |
| 132 break; |
| 133 } |
| 134 } |
| 135 |
| 136 if (i >= arraysize(kSupportedLpcmTypeSets)) { |
| 137 cbk.Run(MediaResult::UNSUPPORTED_CONFIG); |
| 138 return; |
| 139 } |
| 140 |
| 141 // Sanity check the ratio which relates audio frames to media time. |
| 142 int32_t numerator = static_cast<int32_t>(configuration->audio_frame_ratio); |
| 143 uint32_t denominator = static_cast<int32_t>(configuration->media_time_ratio); |
| 144 if ((numerator < 1) || (denominator < 1)) { |
| 145 cbk.Run(MediaResult::INVALID_ARGUMENT); |
| 146 return; |
| 147 } |
| 148 |
| 149 |
| 150 // Figure out the rate we need to scale by in order to produce our fixed |
| 151 // point timestamps. |
| 152 LinearTransform::Ratio frac_scale(1 << PTS_FRACTIONAL_BITS, 1); |
| 153 LinearTransform::Ratio frame_scale(LinearTransform::Ratio(numerator, |
| 154 denominator)); |
| 155 bool no_loss = LinearTransform::Ratio::Compose(frac_scale, |
| 156 frame_scale, |
| 157 &frame_to_media_ratio_); |
| 158 if (!no_loss) { |
| 159 cbk.Run(MediaResult::INVALID_ARGUMENT); |
| 160 return; |
| 161 } |
| 162 |
| 163 // Figure out how many bytes we need to hold the requested number of nSec of |
| 164 // audio. |
| 165 switch (cfg->sample_format) { |
| 166 case LpcmSampleFormat::UNSIGNED_8: |
| 167 bytes_per_frame_ = 1; |
| 168 break; |
| 169 |
| 170 case LpcmSampleFormat::SIGNED_16: |
| 171 bytes_per_frame_ = 2; |
| 172 break; |
| 173 |
| 174 case LpcmSampleFormat::SIGNED_24_IN_32: |
| 175 bytes_per_frame_ = 4; |
| 176 break; |
| 177 |
| 178 default: |
| 179 DCHECK(false); |
| 180 bytes_per_frame_ = 2; |
| 181 break; |
| 182 } |
| 183 bytes_per_frame_ *= cfg->samples_per_frame; |
| 184 |
| 185 // Overflow trying to convert from frames to bytes? |
| 186 uint64_t requested_frames = configuration->max_frames; |
| 187 if (requested_frames > |
| 188 (std::numeric_limits<size_t>::max() / bytes_per_frame_)) { |
| 189 cbk.Run(MediaResult::INSUFFICIENT_RESOURCES); |
| 190 return; |
| 191 } |
| 192 |
| 193 size_t requested_bytes = (requested_frames * bytes_per_frame_); |
| 194 |
| 195 // Attempt to initialize our shared buffer and bind it to our interface |
| 196 // request. |
| 197 if (pipe_.Init(req.Pass(), requested_bytes) != MOJO_RESULT_OK) { |
| 198 cbk.Run(MediaResult::INSUFFICIENT_RESOURCES); |
| 199 return; |
| 200 } |
| 201 |
| 202 // Stash our configuration. |
| 203 format_ = cfg.Pass(); |
| 204 |
| 205 // Have the audio output manager initialize our set of outputs. Note; there |
| 206 // is currently no need for a lock here. Methods called from our user-facing |
| 207 // interfaces are seriailzed by nature of the mojo framework, and none of the |
| 208 // output manager's threads should ever need to manipulate the set. Cleanup |
| 209 // of outputs which have gone away is currently handled in a lazy fashion when |
| 210 // the track fails to promote its weak reference during an operation involving |
| 211 // its outputs. |
| 212 // |
| 213 // TODO(johngro): someday, we will need to deal with recalculating properties |
| 214 // which depend on a track's current set of outputs (for example, the minimum |
| 215 // latency). This will probably be done using a dirty flag in the track |
| 216 // implementations, and scheduling a job to recalculate the properties for the |
| 217 // dirty tracks and notify the users as appropriate. |
| 218 |
| 219 // If we cannot promote our own weak pointer, something is seriously wrong. |
| 220 AudioTrackImplPtr strong_this(weak_this_.lock()); |
| 221 DCHECK(strong_this); |
| 222 DCHECK(owner_); |
| 223 owner_->GetOutputManager().SelectOutputsForTrack(strong_this); |
| 224 |
| 225 // Done |
| 226 cbk.Run(MediaResult::OK); |
| 227 } |
| 228 |
| 229 void AudioTrackImpl::GetRateControl(InterfaceRequest<RateControl> req, |
| 230 const GetRateControlCallback& cbk) { |
| 231 cbk.Run(rate_control_.Bind(req.Pass())); |
| 232 } |
| 233 |
| 234 void AudioTrackImpl::AddOutput(AudioTrackToOutputLinkPtr link) { |
| 235 // TODO(johngro): assert that we are on the main message loop thread. |
| 236 DCHECK(link); |
| 237 auto res = outputs_.emplace(link); |
| 238 DCHECK(res.second); |
| 239 } |
| 240 |
| 241 void AudioTrackImpl::RemoveOutput(AudioTrackToOutputLinkPtr link) { |
| 242 // TODO(johngro): assert that we are on the main message loop thread. |
| 243 DCHECK(link); |
| 244 |
| 245 auto iter = outputs_.find(link); |
| 246 if (iter != outputs_.end()) { |
| 247 outputs_.erase(iter); |
| 248 } else { |
| 249 // TODO(johngro): that's odd. I can't think of a reason why we we should |
| 250 // not be able to find this link in our set of outputs... should we log |
| 251 // something about this? |
| 252 DCHECK(false); |
| 253 } |
| 254 } |
| 255 |
| 256 void AudioTrackImpl::OnPacketReceived(AudioPipe::AudioPacketRefPtr packet) { |
| 257 DCHECK(packet); |
| 258 for (const auto& output : outputs_) { |
| 259 DCHECK(output); |
| 260 output->PushToPendingQueue(packet); |
| 261 } |
| 262 } |
| 263 |
| 264 void AudioTrackImpl::OnFlushRequested(const MediaPipe::FlushCallback& cbk) { |
| 265 for (const auto& output : outputs_) { |
| 266 DCHECK(output); |
| 267 output->FlushPendingQueue(); |
| 268 } |
| 269 cbk.Run(MediaResult::OK); |
| 270 } |
| 271 |
| 272 } // namespace audio |
| 273 } // namespace media |
| 274 } // namespace mojo |
OLD | NEW |