OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "services/media/framework/conversion_pipeline_builder.h" | 5 #include "services/media/framework/conversion_pipeline_builder.h" |
6 #include "services/media/framework/formatting.h" | 6 #include "services/media/framework/formatting.h" |
7 #include "services/media/framework/parts/decoder.h" | 7 #include "services/media/framework/parts/decoder.h" |
8 #include "services/media/framework/parts/lpcm_reformatter.h" | 8 #include "services/media/framework/parts/lpcm_reformatter.h" |
9 | 9 |
10 namespace mojo { | 10 namespace mojo { |
11 namespace media { | 11 namespace media { |
12 | 12 |
13 namespace { | 13 namespace { |
14 | 14 |
15 enum class AddResult { | 15 enum class AddResult { |
16 kFailed, // Can't convert. | 16 kFailed, // Can't convert. |
17 kProgressed, // Added a conversion transform. | 17 kProgressed, // Added a conversion transform. |
18 kFinished // Done adding conversion transforms. | 18 kFinished // Done adding conversion transforms. |
19 }; | 19 }; |
20 | 20 |
21 // Produces a score for in_type with respect to out_type_set. The score | 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 | 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 | 23 // conversion. Higher scores are preferred. A score of zero indicates that |
24 // in_type is incompatible with out_type_set. | 24 // in_type is incompatible with out_type_set. |
25 int Score(const LpcmStreamType& in_type, | 25 int Score(const AudioStreamType& in_type, |
26 const LpcmStreamTypeSet& out_type_set) { | 26 const AudioStreamTypeSet& out_type_set) { |
27 // TODO(dalesat): Plenty of room for more subtlety here. Maybe actually | 27 // TODO(dalesat): Plenty of room for more subtlety here. Maybe actually |
28 // measure conversion costs (cpu, quality, etc) and reflect them here. | 28 // measure conversion costs (cpu, quality, etc) and reflect them here. |
29 | 29 |
30 int score = 1; // We can convert anything, so 1 is the minimum score. | 30 int score = 1; // We can convert anything, so 1 is the minimum score. |
31 | 31 |
32 if (in_type.sample_format() == out_type_set.sample_format() || | 32 if (in_type.sample_format() == out_type_set.sample_format() || |
33 out_type_set.sample_format() == LpcmStreamType::SampleFormat::kAny) { | 33 out_type_set.sample_format() == AudioStreamType::SampleFormat::kAny) { |
34 // Prefer not to convert sample format. | 34 // Prefer not to convert sample format. |
35 score += 10; | 35 score += 10; |
36 } else { | 36 } else { |
37 // Prefer higher-quality formats. | 37 // Prefer higher-quality formats. |
38 switch (out_type_set.sample_format()) { | 38 switch (out_type_set.sample_format()) { |
39 case LpcmStreamType::SampleFormat::kUnsigned8: | 39 case AudioStreamType::SampleFormat::kUnsigned8: |
40 break; | 40 break; |
41 case LpcmStreamType::SampleFormat::kSigned16: | 41 case AudioStreamType::SampleFormat::kSigned16: |
42 score += 1; | 42 score += 1; |
43 break; | 43 break; |
44 case LpcmStreamType::SampleFormat::kSigned24In32: | 44 case AudioStreamType::SampleFormat::kSigned24In32: |
45 score += 2; | 45 score += 2; |
46 break; | 46 break; |
47 case LpcmStreamType::SampleFormat::kFloat: | 47 case AudioStreamType::SampleFormat::kFloat: |
48 score += 3; | 48 score += 3; |
49 break; | 49 break; |
50 default: | 50 default: |
51 NOTREACHED() << "unsupported sample format " | 51 NOTREACHED() << "unsupported sample format " |
52 << out_type_set.sample_format(); | 52 << out_type_set.sample_format(); |
53 } | 53 } |
54 } | 54 } |
55 | 55 |
56 if (out_type_set.channels().contains(in_type.channels())) { | 56 if (out_type_set.channels().contains(in_type.channels())) { |
57 // Prefer not to mixdown/up. | 57 // Prefer not to mixdown/up. |
58 score += 10; | 58 score += 10; |
59 } else { | 59 } else { |
60 return 0; // TODO(dalesat): Remove when we have mixdown/up. | 60 return 0; // TODO(dalesat): Remove when we have mixdown/up. |
61 } | 61 } |
62 | 62 |
63 if (out_type_set.frames_per_second().contains(in_type.frames_per_second())) { | 63 if (out_type_set.frames_per_second().contains(in_type.frames_per_second())) { |
64 // Very much prefer not to resample. | 64 // Very much prefer not to resample. |
65 score += 50; | 65 score += 50; |
66 } else { | 66 } else { |
67 return 0; // TODO(dalesat): Remove when we have resamplers. | 67 return 0; // TODO(dalesat): Remove when we have resamplers. |
68 } | 68 } |
69 | 69 |
70 return score; | 70 return score; |
71 } | 71 } |
72 | 72 |
73 // Finds the media type set that best matches in_type. | 73 // Finds the stream type set that best matches in_type. |
74 const std::unique_ptr<StreamTypeSet>* FindBestLpcm( | 74 const std::unique_ptr<StreamTypeSet>* FindBestLpcm( |
75 const LpcmStreamType& in_type, | 75 const AudioStreamType& in_type, |
76 const std::vector<std::unique_ptr<StreamTypeSet>>& out_type_sets) { | 76 const std::vector<std::unique_ptr<StreamTypeSet>>& out_type_sets) { |
77 const std::unique_ptr<StreamTypeSet>* best = nullptr; | 77 const std::unique_ptr<StreamTypeSet>* best = nullptr; |
78 int best_score = 0; | 78 int best_score = 0; |
| 79 |
79 for (const std::unique_ptr<StreamTypeSet>& out_type_set : out_type_sets) { | 80 for (const std::unique_ptr<StreamTypeSet>& out_type_set : out_type_sets) { |
80 switch (out_type_set->scheme()) { | 81 if (out_type_set->medium() == StreamType::Medium::kAudio && |
81 case StreamType::Scheme::kAnyElementary: | 82 out_type_set->IncludesEncoding(StreamType::kAudioEncodingLpcm)) { |
82 case StreamType::Scheme::kAnyAudio: | 83 int score = Score(in_type, *out_type_set->audio()); |
83 case StreamType::Scheme::kAny: | 84 if (best_score < score) { |
84 // Wildcard scheme allows any type without conversion. | 85 best_score = score; |
85 return &out_type_set; | 86 best = &out_type_set; |
86 case StreamType::Scheme::kLpcm: { | |
87 int score = Score(in_type, *out_type_set->lpcm()); | |
88 if (best_score < score) { | |
89 best_score = score; | |
90 best = &out_type_set; | |
91 } | |
92 break; | |
93 } | 87 } |
94 default: | |
95 break; | |
96 } | 88 } |
97 } | 89 } |
| 90 |
98 return best; | 91 return best; |
99 } | 92 } |
100 | 93 |
101 // Attempts to add transforms to the pipeline given an input compressed audio | 94 // Attempts to add transforms to the pipeline given an input compressed audio |
102 // stream type with (in_type) and the set of output types we need to convert to | 95 // stream type with (in_type) and the set of output types we need to convert to |
103 // (out_type_sets). If the call succeeds, *out_type is set to the new output | 96 // (out_type_sets). If the call succeeds, *out_type is set to the new output |
104 // type. Otherwise, *out_type is set to nullptr. | 97 // type. Otherwise, *out_type is set to nullptr. |
105 AddResult AddTransformsForCompressedAudio( | 98 AddResult AddTransformsForCompressedAudio( |
106 const CompressedAudioStreamType& in_type, | 99 const AudioStreamType& in_type, |
107 const std::vector<std::unique_ptr<StreamTypeSet>>& out_type_sets, | 100 const std::vector<std::unique_ptr<StreamTypeSet>>& out_type_sets, |
108 Graph* graph, | 101 Graph* graph, |
109 OutputRef* output, | 102 OutputRef* output, |
110 std::unique_ptr<StreamType>* out_type) { | 103 std::unique_ptr<StreamType>* out_type) { |
111 DCHECK(out_type); | 104 DCHECK(out_type); |
112 DCHECK(graph); | 105 DCHECK(graph); |
113 | 106 |
114 // See if we have a matching COMPRESSED_AUDIO type. | 107 // See if we have a matching audio type. |
115 for (const std::unique_ptr<StreamTypeSet>& out_type_set : out_type_sets) { | 108 for (const std::unique_ptr<StreamTypeSet>& out_type_set : out_type_sets) { |
116 switch (out_type_set->scheme()) { | 109 if (out_type_set->medium() == StreamType::Medium::kAudio) { |
117 case StreamType::Scheme::kAnyElementary: | 110 if (out_type_set->audio()->contains(in_type)) { |
118 case StreamType::Scheme::kAnyAudio: | 111 // No transform needed. |
119 case StreamType::Scheme::kAny: | |
120 // Wildcard scheme allows any type without conversion. | |
121 *out_type = in_type.Clone(); | 112 *out_type = in_type.Clone(); |
122 return AddResult::kFinished; | 113 return AddResult::kFinished; |
123 case StreamType::Scheme::kCompressedAudio: { | |
124 if (out_type_set->compressed_audio()->contains(in_type)) { | |
125 // No transform needed. | |
126 *out_type = in_type.Clone(); | |
127 return AddResult::kFinished; | |
128 } | |
129 break; | |
130 } | 114 } |
131 default: | 115 // TODO(dalesat): Support a different compressed output type by |
132 break; | 116 // transcoding. |
133 } | 117 } |
134 // TODO(dalesat): Support a different compressed output type by transcoding. | |
135 } | 118 } |
136 | 119 |
137 // Find the best LPCM output type. | 120 // Find the best LPCM output type. |
138 const std::unique_ptr<StreamTypeSet>* best = | 121 const std::unique_ptr<StreamTypeSet>* best = |
139 FindBestLpcm(in_type, out_type_sets); | 122 FindBestLpcm(in_type, out_type_sets); |
140 if (best == nullptr) { | 123 if (best == nullptr) { |
141 // No candidates found. | 124 // No candidates found. |
142 *out_type = nullptr; | 125 *out_type = nullptr; |
143 return AddResult::kFailed; | 126 return AddResult::kFailed; |
144 } | 127 } |
145 | 128 |
146 DCHECK_EQ((*best)->scheme(), StreamType::Scheme::kLpcm); | 129 DCHECK_EQ((*best)->medium(), StreamType::Medium::kAudio); |
| 130 DCHECK((*best)->IncludesEncoding(StreamType::kAudioEncodingLpcm)); |
147 | 131 |
148 // Need to decode. Create a decoder and go from there. | 132 // Need to decode. Create a decoder and go from there. |
149 std::shared_ptr<Decoder> decoder; | 133 std::shared_ptr<Decoder> decoder; |
150 Result result = Decoder::Create(in_type, &decoder); | 134 Result result = Decoder::Create(in_type, &decoder); |
151 if (result != Result::kOk) { | 135 if (result != Result::kOk) { |
152 // No decoder found. | 136 // No decoder found. |
153 *out_type = nullptr; | 137 *out_type = nullptr; |
154 return AddResult::kFailed; | 138 return AddResult::kFailed; |
155 } | 139 } |
156 | 140 |
157 *output = graph->ConnectOutputToPart(*output, graph->Add(decoder)).output(); | 141 *output = graph->ConnectOutputToPart(*output, graph->Add(decoder)).output(); |
158 *out_type = decoder->output_stream_type(); | 142 *out_type = decoder->output_stream_type(); |
159 | 143 |
160 return AddResult::kProgressed; | 144 return AddResult::kProgressed; |
161 } | 145 } |
162 | 146 |
163 // Attempts to add transforms to the pipeline given an input LPCM stream type | 147 // Attempts to add transforms to the pipeline given an input LPCM stream type |
164 // (in_type) and the output lpcm stream type set for the type we need to convert | 148 // (in_type) and the output lpcm stream type set for the type we need to |
165 // to (out_type_set). If the call succeeds, *out_type is set to the new output | 149 // convert to (out_type_set). If the call succeeds, *out_type is set to the new |
166 // type. Otherwise, *out_type is set to nullptr. | 150 // output type. Otherwise, *out_type is set to nullptr. |
167 AddResult AddTransformsForLpcm(const LpcmStreamType& in_type, | 151 AddResult AddTransformsForLpcm(const AudioStreamType& in_type, |
168 const LpcmStreamTypeSet& out_type_set, | 152 const AudioStreamTypeSet& out_type_set, |
169 Graph* graph, | 153 Graph* graph, |
170 OutputRef* output, | 154 OutputRef* output, |
171 std::unique_ptr<StreamType>* out_type) { | 155 std::unique_ptr<StreamType>* out_type) { |
172 DCHECK(graph); | 156 DCHECK(graph); |
173 DCHECK(out_type); | 157 DCHECK(out_type); |
174 | 158 |
175 // TODO(dalesat): Room for more intelligence here wrt transform ordering and | 159 // TODO(dalesat): Room for more intelligence here wrt transform ordering and |
176 // transforms that handle more than one conversion. | 160 // transforms that handle more than one conversion. |
177 if (in_type.sample_format() != out_type_set.sample_format() && | 161 if (in_type.sample_format() != out_type_set.sample_format() && |
178 out_type_set.sample_format() != LpcmStreamType::SampleFormat::kAny) { | 162 out_type_set.sample_format() != AudioStreamType::SampleFormat::kAny) { |
179 *output = | 163 *output = |
180 graph | 164 graph |
181 ->ConnectOutputToPart(*output, graph->Add(LpcmReformatter::Create( | 165 ->ConnectOutputToPart(*output, graph->Add(LpcmReformatter::Create( |
182 in_type, out_type_set))) | 166 in_type, out_type_set))) |
183 .output(); | 167 .output(); |
184 } | 168 } |
185 | 169 |
186 if (!out_type_set.channels().contains(in_type.channels())) { | 170 if (!out_type_set.channels().contains(in_type.channels())) { |
187 // TODO(dalesat): Insert mixdown/up transform. | 171 // TODO(dalesat): Insert mixdown/up transform. |
188 NOTREACHED() << "conversion requires mixdown/up - not supported"; | 172 NOTREACHED() << "conversion requires mixdown/up - not supported"; |
189 *out_type = nullptr; | 173 *out_type = nullptr; |
190 return AddResult::kFailed; | 174 return AddResult::kFailed; |
191 } | 175 } |
192 | 176 |
193 if (!out_type_set.frames_per_second().contains(in_type.frames_per_second())) { | 177 if (!out_type_set.frames_per_second().contains(in_type.frames_per_second())) { |
194 // TODO(dalesat): Insert resampler. | 178 // TODO(dalesat): Insert resampler. |
195 NOTREACHED() << "conversion requires resampling - not supported"; | 179 NOTREACHED() << "conversion requires resampling - not supported"; |
196 *out_type = nullptr; | 180 *out_type = nullptr; |
197 return AddResult::kFailed; | 181 return AddResult::kFailed; |
198 } | 182 } |
199 | 183 |
200 // Build the resulting media type. | 184 // Build the resulting media type. |
201 *out_type = LpcmStreamType::Create( | 185 *out_type = AudioStreamType::Create( |
202 out_type_set.sample_format() == LpcmStreamType::SampleFormat::kAny | 186 StreamType::kAudioEncodingLpcm, nullptr, |
| 187 out_type_set.sample_format() == AudioStreamType::SampleFormat::kAny |
203 ? in_type.sample_format() | 188 ? in_type.sample_format() |
204 : out_type_set.sample_format(), | 189 : out_type_set.sample_format(), |
205 in_type.channels(), in_type.frames_per_second()); | 190 in_type.channels(), in_type.frames_per_second()); |
206 | 191 |
207 return AddResult::kFinished; | 192 return AddResult::kFinished; |
208 } | 193 } |
209 | 194 |
210 // Attempts to add transforms to the pipeline given an input media type with | 195 // Attempts to add transforms to the pipeline given an input audio stream type |
211 // scheme LPCM (in_type) and the set of output types we need to convert to | 196 // witn lpcm encoding (in_type) and the set of output types we need to convert |
212 // (out_type_sets). If the call succeeds, *out_type is set to the new output | 197 // to (out_type_sets). If the call succeeds, *out_type is set to the new |
213 // type. Otherwise, *out_type is set to nullptr. | 198 // output type. Otherwise, *out_type is set to nullptr. |
214 AddResult AddTransformsForLpcm( | 199 AddResult AddTransformsForLpcm( |
215 const LpcmStreamType& in_type, | 200 const AudioStreamType& in_type, |
216 const std::vector<std::unique_ptr<StreamTypeSet>>& out_type_sets, | 201 const std::vector<std::unique_ptr<StreamTypeSet>>& out_type_sets, |
217 Graph* graph, | 202 Graph* graph, |
218 OutputRef* output, | 203 OutputRef* output, |
219 std::unique_ptr<StreamType>* out_type) { | 204 std::unique_ptr<StreamType>* out_type) { |
220 DCHECK(graph); | 205 DCHECK(graph); |
221 DCHECK(out_type); | 206 DCHECK(out_type); |
222 | 207 |
223 const std::unique_ptr<StreamTypeSet>* best = | 208 const std::unique_ptr<StreamTypeSet>* best = |
224 FindBestLpcm(in_type, out_type_sets); | 209 FindBestLpcm(in_type, out_type_sets); |
225 if (best == nullptr) { | 210 if (best == nullptr) { |
226 // TODO(dalesat): Support a compressed output type by encoding. | 211 // TODO(dalesat): Support a compressed output type by encoding. |
227 NOTREACHED() << "conversion using encoder not supported"; | 212 NOTREACHED() << "conversion using encoder not supported"; |
228 *out_type = nullptr; | 213 *out_type = nullptr; |
229 return AddResult::kFailed; | 214 return AddResult::kFailed; |
230 } | 215 } |
231 | 216 |
232 switch ((*best)->scheme()) { | 217 DCHECK_EQ((*best)->medium(), StreamType::Medium::kAudio); |
233 case StreamType::Scheme::kAnyElementary: | 218 |
234 case StreamType::Scheme::kAnyAudio: | 219 return AddTransformsForLpcm(in_type, *(*best)->audio(), graph, output, |
235 case StreamType::Scheme::kAny: | 220 out_type); |
236 // Wildcard scheme allows any type without conversion. | |
237 *out_type = in_type.Clone(); | |
238 return AddResult::kFinished; | |
239 case StreamType::Scheme::kLpcm: | |
240 return AddTransformsForLpcm(in_type, *(*best)->lpcm(), graph, output, | |
241 out_type); | |
242 default: | |
243 NOTREACHED() << "FindBestLpcm produced unexpected type set scheme" | |
244 << (*best)->scheme(); | |
245 return AddResult::kFailed; | |
246 } | |
247 } | 221 } |
248 | 222 |
249 // Attempts to add transforms to the pipeline given an input media type of any | 223 // Attempts to add transforms to the pipeline given an input media type of any |
250 // scheme (in_type) and the set of output types we need to convert to | 224 // medium and encoding (in_type) and the set of output types we need to |
251 // (out_type_sets). If the call succeeds, *out_type is set to the new output | 225 // convert to (out_type_sets). If the call succeeds, *out_type is set to the new |
252 // type. Otherwise, *out_type is set to nullptr. | 226 // output type. Otherwise, *out_type is set to nullptr. |
253 AddResult AddTransforms( | 227 AddResult AddTransforms( |
254 const StreamType& in_type, | 228 const StreamType& in_type, |
255 const std::vector<std::unique_ptr<StreamTypeSet>>& out_type_sets, | 229 const std::vector<std::unique_ptr<StreamTypeSet>>& out_type_sets, |
256 Graph* graph, | 230 Graph* graph, |
257 OutputRef* output, | 231 OutputRef* output, |
258 std::unique_ptr<StreamType>* out_type) { | 232 std::unique_ptr<StreamType>* out_type) { |
259 DCHECK(graph); | 233 DCHECK(graph); |
260 DCHECK(out_type); | 234 DCHECK(out_type); |
261 | 235 |
262 switch (in_type.scheme()) { | 236 switch (in_type.medium()) { |
263 case StreamType::Scheme::kLpcm: | 237 case StreamType::Medium::kAudio: |
264 return AddTransformsForLpcm(*in_type.lpcm(), out_type_sets, graph, output, | 238 if (in_type.encoding() == StreamType::kAudioEncodingLpcm) { |
265 out_type); | 239 return AddTransformsForLpcm(*in_type.audio(), out_type_sets, graph, |
266 case StreamType::Scheme::kCompressedAudio: | 240 output, out_type); |
267 return AddTransformsForCompressedAudio( | 241 } else { |
268 *in_type.compressed_audio(), out_type_sets, graph, output, out_type); | 242 return AddTransformsForCompressedAudio(*in_type.audio(), out_type_sets, |
| 243 graph, output, out_type); |
| 244 } |
269 default: | 245 default: |
270 NOTREACHED() << "conversion not supported for scheme" << in_type.scheme(); | 246 NOTREACHED() << "conversion not supported for medium" << in_type.medium(); |
271 *out_type = nullptr; | 247 *out_type = nullptr; |
272 return AddResult::kFailed; | 248 return AddResult::kFailed; |
273 } | 249 } |
274 } | 250 } |
275 | 251 |
276 } // namespace | 252 } // namespace |
277 | 253 |
278 bool BuildConversionPipeline( | 254 bool BuildConversionPipeline( |
279 const StreamType& in_type, | 255 const StreamType& in_type, |
280 const std::vector<std::unique_ptr<StreamTypeSet>>& out_type_sets, | 256 const std::vector<std::unique_ptr<StreamTypeSet>>& out_type_sets, |
(...skipping 25 matching lines...) Expand all Loading... |
306 *out_type = std::move(converted_type); | 282 *out_type = std::move(converted_type); |
307 return true; | 283 return true; |
308 } | 284 } |
309 | 285 |
310 type_to_convert = converted_type.get(); | 286 type_to_convert = converted_type.get(); |
311 } | 287 } |
312 } | 288 } |
313 | 289 |
314 } // namespace media | 290 } // namespace media |
315 } // namespace mojo | 291 } // namespace mojo |
OLD | NEW |