Chromium Code Reviews| 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->supports_push_transport = true; | |
| 81 desc->supports_pull_transport = false; | |
| 82 desc->supported_media_types = | |
| 83 Array<MediaTypeSetPtr>::New(arraysize(kSupportedLpcmTypeSets)); | |
| 84 | |
| 85 for (size_t i = 0; i < desc->supported_media_types.size(); ++i) { | |
| 86 const MediaTypeSetPtr& mts = | |
| 87 (desc->supported_media_types[i] = MediaTypeSet::New()); | |
| 88 | |
| 89 mts->scheme = MediaTypeScheme::LPCM; | |
| 90 mts->details = MediaTypeSetDetails::New(); | |
| 91 | |
| 92 const auto& s = kSupportedLpcmTypeSets[i]; | |
| 93 LpcmMediaTypeSetDetailsPtr lpcm_detail = LpcmMediaTypeSetDetails::New(); | |
| 94 | |
| 95 lpcm_detail->sample_format = s.sample_format; | |
| 96 lpcm_detail->min_samples_per_frame = s.min_samples_per_frame; | |
| 97 lpcm_detail->max_samples_per_frame = s.max_samples_per_frame; | |
| 98 lpcm_detail->min_frames_per_second = s.min_frames_per_second; | |
| 99 lpcm_detail->max_frames_per_second = s.max_frames_per_second; | |
| 100 mts->details->set_lpcm(lpcm_detail.Pass()); | |
| 101 } | |
| 102 | |
| 103 cbk.Run(desc.Pass()); | |
| 104 } | |
| 105 | |
| 106 void AudioTrackImpl::Configure(AudioTrackConfigurationPtr configuration, | |
| 107 InterfaceRequest<MediaPipe> req, | |
| 108 const ConfigureCallback& cbk) { | |
| 109 // Are we already configured? | |
|
jeffbrown
2015/11/04 23:43:33
Here's a place where it would be nice to just conf
johngro
2015/11/06 02:20:26
Acknowledged.
Its on the TODO list.
| |
| 110 if (pipe_.IsInitialized()) { | |
| 111 cbk.Run(MediaResult::BAD_STATE); | |
| 112 return; | |
| 113 } | |
| 114 | |
| 115 // Check the requested configuration. | |
| 116 if ((configuration->media_type->scheme != MediaTypeScheme::LPCM) || | |
| 117 (!configuration->media_type->details->is_lpcm())) { | |
| 118 cbk.Run(MediaResult::UNSUPPORTED_CONFIG); | |
|
jeffbrown
2015/11/04 23:43:33
Assuming clients should have known better before a
johngro
2015/11/06 02:20:25
Acknowledged.
In the close-the-connection TODO buc
| |
| 119 return; | |
| 120 } | |
| 121 | |
| 122 // Search our supported configuration sets to find one compatible with this | |
| 123 // request. | |
| 124 const auto& cfg = configuration->media_type->details->get_lpcm(); | |
| 125 size_t i; | |
| 126 for (i = 0; i < arraysize(kSupportedLpcmTypeSets); ++i) { | |
| 127 const auto& cfg_set = kSupportedLpcmTypeSets[i]; | |
| 128 | |
| 129 if ((cfg->sample_format == cfg_set.sample_format) && | |
| 130 (cfg->samples_per_frame >= cfg_set.min_samples_per_frame) && | |
| 131 (cfg->samples_per_frame <= cfg_set.max_samples_per_frame) && | |
| 132 (cfg->frames_per_second >= cfg_set.min_frames_per_second) && | |
| 133 (cfg->frames_per_second <= cfg_set.max_frames_per_second)) { | |
| 134 break; | |
| 135 } | |
| 136 } | |
| 137 | |
| 138 if (i >= arraysize(kSupportedLpcmTypeSets)) { | |
| 139 cbk.Run(MediaResult::UNSUPPORTED_CONFIG); | |
| 140 return; | |
| 141 } | |
| 142 | |
| 143 // Sanity check the ratio which relates audio frames to media time. | |
| 144 int32_t N = static_cast<int32_t>(configuration->audio_frame_ratio); | |
|
jeffbrown
2015/11/04 23:43:33
numerator / denominator, or num / denom
Also, see
johngro
2015/11/06 02:20:26
re: future efforts...
Precision is important here.
| |
| 145 uint32_t D = static_cast<int32_t>(configuration->media_time_ratio); | |
| 146 if ((N < 1) || (D < 1)) { | |
| 147 cbk.Run(MediaResult::INVALID_ARGUMENT); | |
| 148 return; | |
| 149 } | |
| 150 | |
| 151 | |
| 152 // Figure out the rate we need to scale by in order to produce our fixed | |
| 153 // point timestamps. | |
| 154 LinearTransform::Ratio frac_scale(1 << PTS_FRACTIONAL_BITS, 1); | |
| 155 LinearTransform::Ratio frame_scale(LinearTransform::Ratio(N, D)); | |
| 156 bool no_loss = LinearTransform::Ratio::Compose(frac_scale, | |
| 157 frame_scale, | |
| 158 &frame_to_media_ratio_); | |
| 159 if (!no_loss) { | |
| 160 cbk.Run(MediaResult::INVALID_ARGUMENT); | |
| 161 return; | |
| 162 } | |
| 163 | |
| 164 // Figure out how many bytes we need to hold the requested number of nSec of | |
| 165 // audio. | |
| 166 switch (cfg->sample_format) { | |
| 167 case LpcmSampleFormat::UNSIGNED_8: | |
| 168 bytes_per_frame_ = 1; | |
| 169 break; | |
| 170 | |
| 171 case LpcmSampleFormat::SIGNED_16: | |
| 172 bytes_per_frame_ = 2; | |
| 173 break; | |
| 174 | |
| 175 case LpcmSampleFormat::SIGNED_24_IN_32: | |
| 176 bytes_per_frame_ = 4; | |
| 177 break; | |
| 178 | |
| 179 default: | |
| 180 DCHECK(false); | |
| 181 bytes_per_frame_ = 2; | |
| 182 break; | |
| 183 } | |
| 184 bytes_per_frame_ *= cfg->samples_per_frame; | |
| 185 | |
| 186 // Overflow trying to convert from frames to bytes? | |
| 187 uint64_t requested_frames = configuration->max_frames; | |
| 188 if (requested_frames > | |
| 189 (std::numeric_limits<size_t>::max() / bytes_per_frame_)) { | |
| 190 cbk.Run(MediaResult::INSUFFICIENT_RESOURCES); | |
| 191 return; | |
| 192 } | |
| 193 | |
| 194 size_t requested_bytes = (requested_frames * bytes_per_frame_); | |
| 195 | |
| 196 // Attempt to initialize our shared buffer and bind it to our interface | |
| 197 // request. | |
| 198 if (pipe_.Init(req.Pass(), requested_bytes) != MOJO_RESULT_OK) { | |
| 199 cbk.Run(MediaResult::INSUFFICIENT_RESOURCES); | |
| 200 return; | |
| 201 } | |
| 202 | |
| 203 // Stash our configuration. | |
| 204 format_ = cfg->Clone(); | |
|
jeffbrown
2015/11/04 23:43:33
Looks to me like the config is just going to be dr
johngro
2015/11/06 02:20:26
Done.
The fact that this is even allowed is a bit
| |
| 205 | |
| 206 // Have the audio output manager initialize our set of outputs. Note; there | |
| 207 // is currently no need for a lock here. Methods called from our user-facing | |
| 208 // interfaces are seriailzed by nature of the mojo framework, and none of the | |
| 209 // output manager's threads should ever need to manipulate the set. Cleanup | |
| 210 // of outputs which have gone away is currently handled in a lazy fashion when | |
| 211 // the track fails to promote its weak reference during an operation involving | |
| 212 // its outputs. | |
| 213 // | |
| 214 // TODO(johngro): someday, we will need to deal with recalculating properties | |
| 215 // which depend on a track's current set of outputs (for example, the minimum | |
| 216 // latency). This will probably be done using a dirty flag in the track | |
| 217 // implementations, and scheduling a job to recalculate the properties for the | |
| 218 // dirty tracks and notify the users as appropriate. | |
| 219 | |
| 220 // If we cannot promote our own weak pointer, something is seriously wrong. | |
| 221 AudioTrackImplPtr strong_this(weak_this_.lock()); | |
| 222 DCHECK(strong_this); | |
| 223 DCHECK(owner_); | |
| 224 owner_->GetOutputManager().SelectOutputsForTrack(strong_this); | |
| 225 | |
| 226 // Done | |
| 227 cbk.Run(MediaResult::OK); | |
| 228 } | |
| 229 | |
| 230 void AudioTrackImpl::GetRateControl(InterfaceRequest<RateControl> req, | |
| 231 const GetRateControlCallback& cbk) { | |
| 232 cbk.Run(rate_control_.Bind(req.Pass())); | |
| 233 } | |
| 234 | |
| 235 void AudioTrackImpl::AddOutput(AudioTrackToOutputLinkPtr link) { | |
| 236 // TODO(johngro): assert that we are on the main message loop thread. | |
| 237 DCHECK(link); | |
| 238 auto res = outputs_.emplace(link); | |
| 239 DCHECK(res.second); | |
| 240 } | |
| 241 | |
| 242 void AudioTrackImpl::RemoveOutput(AudioTrackToOutputLinkPtr link) { | |
| 243 // TODO(johngro): assert that we are on the main message loop thread. | |
| 244 DCHECK(link); | |
| 245 | |
| 246 auto iter = outputs_.find(link); | |
| 247 if (iter != outputs_.end()) { | |
| 248 outputs_.erase(iter); | |
| 249 } else { | |
| 250 // TODO(johngro): that's odd. I can't think of a reason why we we should | |
| 251 // not be able to find this link in our set of outputs... should we log | |
| 252 // something about this? | |
|
jeffbrown
2015/11/04 23:43:33
DCHECK! We failed an internal invariant.
johngro
2015/11/06 02:20:26
Done.
| |
| 253 } | |
| 254 } | |
| 255 | |
| 256 void AudioTrackImpl::OnPacketReceived(AudioPipe::AudioPacketRefPtr packet) { | |
|
jeffbrown
2015/11/04 23:43:33
Do we need to DCHECK the packet?
johngro
2015/11/06 02:20:26
no, but it does not hurt to do so. I have been ca
| |
| 257 for (const auto& output : outputs_) { | |
| 258 DCHECK(output); | |
| 259 output->PushToPendingQueue(packet); | |
| 260 } | |
| 261 } | |
| 262 | |
| 263 void AudioTrackImpl::OnFlushRequested(const MediaPipe::FlushCallback& cbk) { | |
| 264 for (const auto& output : outputs_) { | |
| 265 DCHECK(output); | |
|
jeffbrown
2015/11/04 23:43:33
I feel like this DCHECK is kind of redundant given
johngro
2015/11/06 02:20:26
Acknowledged.
Can't have it both ways. Either we
| |
| 266 output->FlushPendingQueue(); | |
| 267 } | |
| 268 cbk.Run(MediaResult::OK); | |
| 269 } | |
| 270 | |
| 271 } // namespace audio | |
| 272 } // namespace media | |
| 273 } // namespace mojo | |
| OLD | NEW |