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/formatting.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. |
| 25 int Score( |
| 26 const LpcmStreamType& in_type, |
| 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) { |
| 79 const StreamTypeSetPtr* best = nullptr; |
| 80 int best_score = 0; |
| 81 for (const StreamTypeSetPtr& out_type_set : *out_type_sets) { |
| 82 switch (out_type_set->scheme()) { |
| 83 case StreamType::Scheme::kAnyElementary: |
| 84 case StreamType::Scheme::kAnyAudio: |
| 85 case StreamType::Scheme::kAny: |
| 86 // Wildcard scheme allows any type without conversion. |
| 87 return &out_type_set; |
| 88 case StreamType::Scheme::kLpcm: { |
| 89 int score = Score(in_type, *out_type_set->lpcm()); |
| 90 if (best_score < score) { |
| 91 best_score = score; |
| 92 best = &out_type_set; |
| 93 } |
| 94 break; |
| 95 } |
| 96 default: |
| 97 break; |
| 98 } |
| 99 } |
| 100 return best; |
| 101 } |
| 102 |
| 103 // Attempts to add transforms to the pipeline given an input compressed audio |
| 104 // stream type with (in_type) and the set of output types we need to convert to |
| 105 // (out_type_sets). If the call succeeds, *out_type is set to the new output |
| 106 // type. Otherwise, *out_type is set to nullptr. |
| 107 AddResult AddTransformsForCompressedAudio( |
| 108 const CompressedAudioStreamType& in_type, |
| 109 const StreamTypePtr& in_type_ptr, |
| 110 const StreamTypeSetsPtr& out_type_sets, |
| 111 Engine* engine, |
| 112 Engine::Output* output, |
| 113 StreamTypePtr* out_type) { |
| 114 DCHECK(out_type); |
| 115 DCHECK(engine); |
| 116 |
| 117 // See if we have a matching COMPRESSED_AUDIO type. |
| 118 for (const StreamTypeSetPtr& out_type_set : *out_type_sets) { |
| 119 switch (out_type_set->scheme()) { |
| 120 case StreamType::Scheme::kAnyElementary: |
| 121 case StreamType::Scheme::kAnyAudio: |
| 122 case StreamType::Scheme::kAny: |
| 123 // Wildcard scheme allows any type without conversion. |
| 124 *out_type = in_type.Clone(); |
| 125 return AddResult::kFinished; |
| 126 case StreamType::Scheme::kCompressedAudio: { |
| 127 if (out_type_set->compressed_audio()->contains(in_type)) { |
| 128 // No transform needed. |
| 129 *out_type = in_type.Clone(); |
| 130 return AddResult::kFinished; |
| 131 } |
| 132 break; |
| 133 } |
| 134 default: |
| 135 break; |
| 136 } |
| 137 // TODO(dalesat): Support a different compressed output type by transcoding. |
| 138 } |
| 139 |
| 140 // Find the best LPCM output type. |
| 141 const StreamTypeSetPtr* best = FindBestLpcm(in_type, out_type_sets); |
| 142 if (best == nullptr) { |
| 143 // No candidates found. |
| 144 *out_type = nullptr; |
| 145 return AddResult::kFailed; |
| 146 } |
| 147 |
| 148 DCHECK_EQ((*best)->scheme(), StreamType::Scheme::kLpcm); |
| 149 |
| 150 // Need to decode. Create a decoder and go from there. |
| 151 DecoderPtr decoder; |
| 152 Result result = Decoder::Create(in_type_ptr, &decoder); |
| 153 if (result != Result::kOk) { |
| 154 // No decoder found. |
| 155 *out_type = nullptr; |
| 156 return AddResult::kFailed; |
| 157 } |
| 158 |
| 159 *output = engine->ConnectOutputToPart(*output, engine->Add(decoder)).output(); |
| 160 *out_type = decoder->output_stream_type(); |
| 161 |
| 162 return AddResult::kProgressed; |
| 163 } |
| 164 |
| 165 // Attempts to add transforms to the pipeline given an input LPCM stream type |
| 166 // (in_type) and the output lpcm stream type set for the type we need to convert |
| 167 // to (out_type_set). If the call succeeds, *out_type is set to the new output |
| 168 // type. Otherwise, *out_type is set to nullptr. |
| 169 AddResult AddTransformsForLpcm( |
| 170 const LpcmStreamType& in_type, |
| 171 const LpcmStreamTypeSet& out_type_set, |
| 172 Engine* engine, |
| 173 Engine::Output* output, |
| 174 StreamTypePtr* out_type) { |
| 175 DCHECK(engine); |
| 176 DCHECK(out_type); |
| 177 |
| 178 // TODO(dalesat): Room for more intelligence here wrt transform ordering and |
| 179 // transforms that handle more than one conversion. |
| 180 if (in_type.sample_format() != out_type_set.sample_format() && |
| 181 out_type_set.sample_format() != LpcmStreamType::SampleFormat::kAny) { |
| 182 // The reformatter will fix interleave conversion. |
| 183 *output = engine->ConnectOutputToPart( |
| 184 *output, |
| 185 engine->Add(LpcmReformatter::Create(in_type, out_type_set))).output(); |
| 186 } |
| 187 |
| 188 if (!out_type_set.channels().contains(in_type.channels())) { |
| 189 // TODO(dalesat): Insert mixdown/up transform. |
| 190 NOTREACHED() << "conversion requires mixdown/up - not supported"; |
| 191 *out_type = nullptr; |
| 192 return AddResult::kFailed; |
| 193 } |
| 194 |
| 195 if (!out_type_set.frames_per_second().contains(in_type.frames_per_second())) { |
| 196 // TODO(dalesat): Insert resampler. |
| 197 NOTREACHED() << "conversion requires resampling - not supported"; |
| 198 *out_type = nullptr; |
| 199 return AddResult::kFailed; |
| 200 } |
| 201 |
| 202 // Build the resulting media type. |
| 203 *out_type = LpcmStreamType::Create( |
| 204 out_type_set.sample_format() == LpcmStreamType::SampleFormat::kAny ? |
| 205 in_type.sample_format() : |
| 206 out_type_set.sample_format(), |
| 207 in_type.channels(), |
| 208 in_type.frames_per_second()); |
| 209 |
| 210 return AddResult::kFinished; |
| 211 } |
| 212 |
| 213 // Attempts to add transforms to the pipeline given an input media type with |
| 214 // scheme LPCM (in_type) and the set of output types we need to convert to |
| 215 // (out_type_sets). If the call succeeds, *out_type is set to the new output |
| 216 // type. Otherwise, *out_type is set to nullptr. |
| 217 AddResult AddTransformsForLpcm( |
| 218 const LpcmStreamType& in_type, |
| 219 const StreamTypeSetsPtr& out_type_sets, |
| 220 Engine* engine, |
| 221 Engine::Output* output, |
| 222 StreamTypePtr* out_type) { |
| 223 DCHECK(engine); |
| 224 DCHECK(out_type); |
| 225 |
| 226 const StreamTypeSetPtr* best = FindBestLpcm(in_type, out_type_sets); |
| 227 if (best == nullptr) { |
| 228 // TODO(dalesat): Support a compressed output type by encoding. |
| 229 NOTREACHED() << "conversion using encoder not supported"; |
| 230 *out_type = nullptr; |
| 231 return AddResult::kFailed; |
| 232 } |
| 233 |
| 234 switch ((*best)->scheme()) { |
| 235 case StreamType::Scheme::kAnyElementary: |
| 236 case StreamType::Scheme::kAnyAudio: |
| 237 case StreamType::Scheme::kAny: |
| 238 // Wildcard scheme allows any type without conversion. |
| 239 *out_type = in_type.Clone(); |
| 240 return AddResult::kFinished; |
| 241 case StreamType::Scheme::kLpcm: |
| 242 return AddTransformsForLpcm( |
| 243 in_type, |
| 244 *(*best)->lpcm(), |
| 245 engine, |
| 246 output, |
| 247 out_type); |
| 248 default: |
| 249 NOTREACHED() << "FindBestLpcm produced unexpected type set scheme" |
| 250 << (*best)->scheme(); |
| 251 return AddResult::kFailed; |
| 252 } |
| 253 } |
| 254 |
| 255 // Attempts to add transforms to the pipeline given an input media type of any |
| 256 // scheme (in_type) and the set of output types we need to convert to |
| 257 // (out_type_sets). If the call succeeds, *out_type is set to the new output |
| 258 // type. Otherwise, *out_type is set to nullptr. |
| 259 AddResult AddTransforms( |
| 260 const StreamTypePtr& in_type, |
| 261 const StreamTypeSetsPtr& out_type_sets, |
| 262 Engine* engine, |
| 263 Engine::Output* output, |
| 264 StreamTypePtr* out_type) { |
| 265 DCHECK(in_type); |
| 266 DCHECK(engine); |
| 267 DCHECK(out_type); |
| 268 |
| 269 switch (in_type->scheme()) { |
| 270 case StreamType::Scheme::kLpcm: |
| 271 return AddTransformsForLpcm( |
| 272 *in_type->lpcm(), |
| 273 out_type_sets, |
| 274 engine, |
| 275 output, |
| 276 out_type); |
| 277 case StreamType::Scheme::kCompressedAudio: |
| 278 return AddTransformsForCompressedAudio( |
| 279 *in_type->compressed_audio(), |
| 280 in_type, |
| 281 out_type_sets, |
| 282 engine, |
| 283 output, |
| 284 out_type); |
| 285 default: |
| 286 NOTREACHED() << "conversion not supported for scheme" |
| 287 << in_type->scheme(); |
| 288 *out_type = nullptr; |
| 289 return AddResult::kFailed; |
| 290 } |
| 291 } |
| 292 |
| 293 } // namespace |
| 294 |
| 295 bool BuildConversionPipeline( |
| 296 const StreamTypePtr& in_type, |
| 297 const StreamTypeSetsPtr& out_type_sets, |
| 298 Engine* engine, |
| 299 Engine::Output* output, |
| 300 StreamTypePtr* out_type) { |
| 301 DCHECK(in_type); |
| 302 DCHECK(out_type_sets); |
| 303 DCHECK(engine); |
| 304 DCHECK(output); |
| 305 DCHECK(out_type); |
| 306 |
| 307 Engine::Output out = *output; |
| 308 |
| 309 const StreamTypePtr* type_to_convert = &in_type; |
| 310 StreamTypePtr next_in_type; |
| 311 while (true) { |
| 312 StreamTypePtr converted_type; |
| 313 switch (AddTransforms( |
| 314 *type_to_convert, |
| 315 out_type_sets, |
| 316 engine, |
| 317 &out, |
| 318 &converted_type)) { |
| 319 case AddResult::kFailed: |
| 320 // Failed to find a suitable conversion. Return the pipeline to its |
| 321 // original state. |
| 322 engine->RemovePartsConnectedToOutput(*output); |
| 323 *out_type = nullptr; |
| 324 return false; |
| 325 case AddResult::kProgressed: |
| 326 // Made progress. Continue. |
| 327 break; |
| 328 case AddResult::kFinished: |
| 329 // No further conversion required. |
| 330 *output = out; |
| 331 *out_type = std::move(converted_type); |
| 332 return true; |
| 333 } |
| 334 |
| 335 next_in_type = std::move(converted_type); |
| 336 type_to_convert = &next_in_type; |
| 337 } |
| 338 } |
| 339 |
| 340 } // namespace media |
| 341 } // namespace mojo |
OLD | NEW |