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 |