OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 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 "services/media/framework/conversion_pipeline_builder.h" | |
6 #include "services/media/framework/ostream.h" | |
7 #include "services/media/framework/parts/decoder.h" | |
8 #include "services/media/framework/parts/lpcm_reformatter.h" | |
9 | |
10 namespace mojo { | |
11 namespace media { | |
12 | |
13 namespace { | |
14 | |
15 enum class AddResult { | |
16 kFailed, // Can't convert. | |
17 kProgressed, // Added a conversion transform. | |
18 kFinished // Done adding conversion transforms. | |
19 }; | |
20 | |
21 // Produces a score for in_type with respect to out_type_set. The score | |
22 // is used to compare type sets to see which represents the best goal for | |
23 // conversion. Higher scores are preferred. A score of zero indicates that | |
24 // in_type is incompatible with out_type_set. | |
jeffbrown
2016/02/02 05:35:46
I'm generally skeptical of baking policies like th
dalesat
2016/02/02 21:46:38
From the framework perspective, there's no baking
| |
25 int Score( | |
26 const LpcmStreamType& in_type, | |
jeffbrown
2016/02/02 05:35:46
Formatting looks wrong here. Use clang-format.
dalesat
2016/02/02 21:46:38
Acknowledged.
| |
27 const LpcmStreamTypeSet& out_type_set) { | |
28 // TODO(dalesat): Plenty of room for more subtlety here. Maybe actually | |
29 // measure conversion costs (cpu, quality, etc) and reflect them here. | |
30 | |
31 int score = 1; // We can convert anything, so 1 is the minimum score. | |
32 | |
33 if (in_type.sample_format() == out_type_set.sample_format() || | |
34 out_type_set.sample_format() == LpcmStreamType::SampleFormat::kAny) { | |
35 // Prefer not to convert sample format. | |
36 score += 10; | |
37 } else { | |
38 // Prefer higher-quality formats. | |
39 switch (out_type_set.sample_format()) { | |
40 case LpcmStreamType::SampleFormat::kUnsigned8: | |
41 break; | |
42 case LpcmStreamType::SampleFormat::kSigned16: | |
43 score += 1; | |
44 break; | |
45 case LpcmStreamType::SampleFormat::kSigned24In32: | |
46 score += 2; | |
47 break; | |
48 case LpcmStreamType::SampleFormat::kFloat: | |
49 score += 3; | |
50 break; | |
51 default: | |
52 NOTREACHED() << "unsupported sample format " | |
53 << out_type_set.sample_format(); | |
54 } | |
55 } | |
56 | |
57 if (out_type_set.channels().contains(in_type.channels())) { | |
58 // Prefer not to mixdown/up. | |
59 score += 10; | |
60 } else { | |
61 return 0; // TODO(dalesat): Remove when we have mixdown/up. | |
62 } | |
63 | |
64 if (out_type_set.frames_per_second(). | |
65 contains(in_type.frames_per_second())) { | |
66 // Very much prefer not to resample. | |
67 score += 50; | |
68 } else { | |
69 return 0; // TODO(dalesat): Remove when we have resamplers. | |
70 } | |
71 | |
72 return score; | |
73 } | |
74 | |
75 // Finds the media type set that best matches in_type. | |
76 const StreamTypeSetPtr* FindBestLpcm( | |
77 const LpcmStreamType& in_type, | |
78 const StreamTypeSetsPtr& out_type_sets) { | |
jeffbrown
2016/02/02 05:35:46
It's really hard to follow this code when you have
dalesat
2016/02/02 21:46:38
Acknowledged.
| |
79 const StreamTypeSetPtr* best = nullptr; | |
80 int best_score = 0; | |
81 // Array has no const iterator, hence the for(;;). | |
jeffbrown
2016/02/02 05:35:46
array->storage().cbegin()...
dalesat
2016/02/02 21:46:38
Comment predates transition from MediaType to Stre
| |
82 for (size_t i = 0; i < out_type_sets->size(); i++) { | |
83 const StreamTypeSetPtr& out_type_set = (*out_type_sets)[i]; | |
84 switch (out_type_set->scheme()) { | |
85 case StreamType::Scheme::kAnyElementary: | |
86 case StreamType::Scheme::kAnyAudio: | |
87 case StreamType::Scheme::kAny: | |
88 // Wildcard scheme allows any type without conversion. | |
89 return &out_type_set; | |
90 case StreamType::Scheme::kLpcm: { | |
91 int score = Score(in_type, *out_type_set->lpcm()); | |
92 if (best_score < score) { | |
93 best_score = score; | |
94 best = &out_type_set; | |
95 } | |
96 break; | |
97 } | |
98 default: | |
99 break; | |
100 } | |
101 } | |
102 return best; | |
103 } | |
104 | |
105 // Attempts to add transforms to the pipeline given an input compressed audio | |
106 // stream type with (in_type) and the set of output types we need to convert to | |
107 // (out_type_sets). If the call succeeds, *out_type is set to the new output | |
108 // type. Otherwise, *out_type is set to nullptr. | |
109 AddResult AddTransformsForCompressedAudio( | |
110 const CompressedAudioStreamType& in_type, | |
111 const StreamTypePtr& in_type_ptr, | |
112 const StreamTypeSetsPtr& out_type_sets, | |
113 Engine* engine, | |
114 Engine::Output* output, | |
115 StreamTypePtr* out_type) { | |
116 DCHECK(out_type); | |
117 DCHECK(engine); | |
118 | |
119 // See if we have a matching COMPRESSED_AUDIO type. | |
120 // Array has no const iterator, hence the for(;;). | |
121 for (size_t i = 0; i < out_type_sets->size(); i++) { | |
122 const StreamTypeSetPtr& out_type_set = (*out_type_sets)[i]; | |
123 switch (out_type_set->scheme()) { | |
124 case StreamType::Scheme::kAnyElementary: | |
125 case StreamType::Scheme::kAnyAudio: | |
126 case StreamType::Scheme::kAny: | |
127 // Wildcard scheme allows any type without conversion. | |
128 *out_type = in_type.Clone(); | |
129 return AddResult::kFinished; | |
130 case StreamType::Scheme::kCompressedAudio: { | |
131 if (out_type_set->compressed_audio()->contains(in_type)) { | |
132 // No transform needed. | |
133 *out_type = in_type.Clone(); | |
134 return AddResult::kFinished; | |
135 } | |
136 break; | |
137 } | |
138 default: | |
139 break; | |
140 } | |
141 // TODO(dalesat): Support a different compressed output type by transcoding. | |
142 } | |
143 | |
144 // Find the best LPCM output type. | |
145 const StreamTypeSetPtr* best = FindBestLpcm(in_type, out_type_sets); | |
146 if (best == nullptr) { | |
147 // No candidates found. | |
148 *out_type = nullptr; | |
149 return AddResult::kFailed; | |
150 } | |
151 | |
152 DCHECK_EQ((*best)->scheme(), StreamType::Scheme::kLpcm); | |
153 | |
154 // Need to decode. Create a decoder and go from there. | |
155 DecoderPtr decoder; | |
156 Result result = Decoder::Create(in_type_ptr, &decoder); | |
157 if (result != Result::kOk) { | |
158 // No decoder found. | |
159 *out_type = nullptr; | |
160 return AddResult::kFailed; | |
161 } | |
162 | |
163 *output = engine->Connect(*output, engine->Add(decoder)).output(); | |
164 *out_type = decoder->output_stream_type(); | |
165 | |
166 return AddResult::kProgressed; | |
167 } | |
168 | |
169 // Attempts to add transforms to the pipeline given an input LPCM stream type | |
170 // (in_type) and the output lpcm stream type set for the type we need to convert | |
171 // to (out_type_set). If the call succeeds, *out_type is set to the new output | |
172 // type. Otherwise, *out_type is set to nullptr. | |
173 AddResult AddTransformsForLpcm( | |
174 const LpcmStreamType& in_type, | |
175 const LpcmStreamTypeSet& out_type_set, | |
176 Engine* engine, | |
177 Engine::Output* output, | |
178 StreamTypePtr* out_type) { | |
179 DCHECK(engine); | |
180 DCHECK(out_type); | |
181 | |
182 // TODO(dalesat): Room for more intelligence here wrt transform ordering and | |
183 // transforms that handle more than one conversion. | |
184 if (in_type.sample_format() != out_type_set.sample_format() && | |
185 out_type_set.sample_format() != LpcmStreamType::SampleFormat::kAny) { | |
186 // The reformatter will fix interleave conversion. | |
187 *output = engine->Connect( | |
188 *output, | |
189 engine->Add(LpcmReformatter::New(in_type, out_type_set))).output(); | |
190 } | |
191 | |
192 if (!out_type_set.channels().contains(in_type.channels())) { | |
193 // TODO(dalesat): Insert mixdown/up transform. | |
194 NOTREACHED() << "conversion requires mixdown/up - not supported"; | |
195 *out_type = nullptr; | |
196 return AddResult::kFailed; | |
197 } | |
198 | |
199 if (!out_type_set.frames_per_second().contains(in_type.frames_per_second())) { | |
200 // TODO(dalesat): Insert resampler. | |
201 NOTREACHED() << "conversion requires resampling - not supported"; | |
202 *out_type = nullptr; | |
203 return AddResult::kFailed; | |
204 } | |
205 | |
206 // Build the resulting media type. | |
207 *out_type = LpcmStreamType::New( | |
208 out_type_set.sample_format() == LpcmStreamType::SampleFormat::kAny ? | |
209 in_type.sample_format() : | |
210 out_type_set.sample_format(), | |
211 in_type.channels(), | |
212 in_type.frames_per_second()); | |
213 | |
214 return AddResult::kFinished; | |
215 } | |
216 | |
217 // Attempts to add transforms to the pipeline given an input media type with | |
218 // scheme LPCM (in_type) and the set of output types we need to convert to | |
219 // (out_type_sets). If the call succeeds, *out_type is set to the new output | |
220 // type. Otherwise, *out_type is set to nullptr. | |
221 AddResult AddTransformsForLpcm( | |
222 const LpcmStreamType& in_type, | |
223 const StreamTypeSetsPtr& out_type_sets, | |
224 Engine* engine, | |
225 Engine::Output* output, | |
226 StreamTypePtr* out_type) { | |
227 DCHECK(engine); | |
228 DCHECK(out_type); | |
229 | |
230 const StreamTypeSetPtr* best = FindBestLpcm(in_type, out_type_sets); | |
231 if (best == nullptr) { | |
232 // TODO(dalesat): Support a compressed output type by encoding. | |
233 NOTREACHED() << "conversion using encoder not supported"; | |
234 *out_type = nullptr; | |
235 return AddResult::kFailed; | |
236 } | |
237 | |
238 switch ((*best)->scheme()) { | |
239 case StreamType::Scheme::kAnyElementary: | |
240 case StreamType::Scheme::kAnyAudio: | |
241 case StreamType::Scheme::kAny: | |
242 // Wildcard scheme allows any type without conversion. | |
243 *out_type = in_type.Clone(); | |
244 return AddResult::kFinished; | |
245 case StreamType::Scheme::kLpcm: | |
246 return AddTransformsForLpcm( | |
247 in_type, | |
248 *(*best)->lpcm(), | |
249 engine, | |
250 output, | |
251 out_type); | |
252 default: | |
253 NOTREACHED() << "FindBestLpcm produced unexpected type set scheme" | |
254 << (*best)->scheme(); | |
255 return AddResult::kFailed; | |
256 } | |
257 } | |
258 | |
259 // Attempts to add transforms to the pipeline given an input media type of any | |
260 // scheme (in_type) and the set of output types we need to convert to | |
261 // (out_type_sets). If the call succeeds, *out_type is set to the new output | |
262 // type. Otherwise, *out_type is set to nullptr. | |
263 AddResult AddTransforms( | |
264 const StreamTypePtr& in_type, | |
265 const StreamTypeSetsPtr& out_type_sets, | |
266 Engine* engine, | |
267 Engine::Output* output, | |
268 StreamTypePtr* out_type) { | |
269 DCHECK(in_type); | |
270 DCHECK(engine); | |
271 DCHECK(out_type); | |
272 | |
273 switch (in_type->scheme()) { | |
274 case StreamType::Scheme::kLpcm: | |
275 return AddTransformsForLpcm( | |
276 *in_type->lpcm(), | |
277 out_type_sets, | |
278 engine, | |
279 output, | |
280 out_type); | |
281 case StreamType::Scheme::kCompressedAudio: | |
282 return AddTransformsForCompressedAudio( | |
283 *in_type->compressed_audio(), | |
284 in_type, | |
285 out_type_sets, | |
286 engine, | |
287 output, | |
288 out_type); | |
289 default: | |
290 NOTREACHED() << "conversion not supported for scheme" | |
291 << in_type->scheme(); | |
292 *out_type = nullptr; | |
293 return AddResult::kFailed; | |
294 } | |
295 } | |
296 | |
297 } // namespace | |
298 | |
299 bool BuildConversionPipeline( | |
300 const StreamTypePtr& in_type, | |
301 const StreamTypeSetsPtr& out_type_sets, | |
302 Engine* engine, | |
303 Engine::Output* output, | |
304 StreamTypePtr* out_type) { | |
305 DCHECK(in_type); | |
306 DCHECK(out_type_sets); | |
307 DCHECK(engine); | |
308 DCHECK(output); | |
309 DCHECK(out_type); | |
310 | |
311 Engine::Output out = *output; | |
312 | |
313 const StreamTypePtr* type_to_convert = &in_type; | |
314 StreamTypePtr next_in_type; | |
315 while (true) { | |
316 StreamTypePtr converted_type; | |
317 switch (AddTransforms( | |
318 *type_to_convert, | |
319 out_type_sets, | |
320 engine, | |
321 &out, | |
322 &converted_type)) { | |
323 case AddResult::kFailed: | |
324 // Failed to find a suitable conversion. Return the pipeline to its | |
325 // original state. | |
326 engine->RemoveAll(*output); | |
327 *out_type = nullptr; | |
328 return false; | |
329 case AddResult::kProgressed: | |
330 // Made progress. Continue. | |
331 break; | |
332 case AddResult::kFinished: | |
333 // No further conversion required. | |
334 *output = out; | |
335 *out_type = std::move(converted_type); | |
336 return true; | |
337 } | |
338 | |
339 next_in_type = std::move(converted_type); | |
340 type_to_convert = &next_in_type; | |
341 } | |
342 } | |
343 | |
344 } // namespace media | |
345 } // namespace mojo | |
OLD | NEW |