Chromium Code Reviews| 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 |