blob: 6b60cb019c3f72ebbabded738227903f2c8b4bff [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "garnet/bin/media/media_service/conversion_pipeline_builder.h"
#include "garnet/bin/media/audio/lpcm_reformatter.h"
#include "garnet/bin/media/decode/decoder.h"
#include "garnet/bin/media/framework/formatting.h"
namespace media {
namespace {
enum class AddResult {
kFailed, // Can't convert.
kProgressed, // Added a conversion transform.
kFinished // Done adding conversion transforms.
};
// Produces a score for in_type with respect to out_type_set. The score
// is used to compare type sets to see which represents the best goal for
// conversion. Higher scores are preferred. A score of zero indicates that
// in_type is incompatible with out_type_set.
int Score(const AudioStreamType& in_type,
const AudioStreamTypeSet& out_type_set) {
// TODO(dalesat): Plenty of room for more subtlety here. Maybe actually
// measure conversion costs (cpu, quality, etc) and reflect them here.
int score = 1; // We can convert anything, so 1 is the minimum score.
if (in_type.sample_format() == out_type_set.sample_format() ||
out_type_set.sample_format() == AudioStreamType::SampleFormat::kAny) {
// Prefer not to convert sample format.
score += 10;
} else {
// Prefer higher-quality formats.
switch (out_type_set.sample_format()) {
case AudioStreamType::SampleFormat::kUnsigned8:
break;
case AudioStreamType::SampleFormat::kSigned16:
score += 1;
break;
case AudioStreamType::SampleFormat::kSigned24In32:
score += 2;
break;
case AudioStreamType::SampleFormat::kFloat:
score += 3;
break;
default:
FXL_DCHECK(false) << "unsupported sample format "
<< out_type_set.sample_format();
}
}
if (out_type_set.channels().contains(in_type.channels())) {
// Prefer not to mixdown/up.
score += 10;
} else {
return 0; // TODO(dalesat): Remove when we have mixdown/up.
}
if (out_type_set.frames_per_second().contains(in_type.frames_per_second())) {
// Very much prefer not to resample.
score += 50;
} else {
return 0; // TODO(dalesat): Remove when we have resamplers.
}
return score;
}
// Finds the stream type set that best matches in_type.
const std::unique_ptr<StreamTypeSet>* FindBestLpcm(
const AudioStreamType& in_type,
const std::vector<std::unique_ptr<StreamTypeSet>>& out_type_sets) {
const std::unique_ptr<StreamTypeSet>* best = nullptr;
int best_score = 0;
for (const std::unique_ptr<StreamTypeSet>& out_type_set : out_type_sets) {
if (out_type_set->medium() == StreamType::Medium::kAudio &&
out_type_set->IncludesEncoding(StreamType::kAudioEncodingLpcm)) {
int score = Score(in_type, *out_type_set->audio());
if (best_score < score) {
best_score = score;
best = &out_type_set;
}
}
}
return best;
}
// Attempts to add transforms to the pipeline given an input compressed audio
// stream type with (in_type) and the set of output types we need to convert to
// (out_type_sets). If the call succeeds, *out_type is set to the new output
// type. Otherwise, *out_type is set to nullptr.
AddResult AddTransformsForCompressedAudio(
const AudioStreamType& in_type,
const std::vector<std::unique_ptr<StreamTypeSet>>& out_type_sets,
Graph* graph,
OutputRef* output,
std::unique_ptr<StreamType>* out_type) {
FXL_DCHECK(out_type);
FXL_DCHECK(graph);
// See if we have a matching audio type.
for (const std::unique_ptr<StreamTypeSet>& out_type_set : out_type_sets) {
if (out_type_set->medium() == StreamType::Medium::kAudio) {
if (out_type_set->audio()->Includes(in_type)) {
// No transform needed.
*out_type = in_type.Clone();
return AddResult::kFinished;
}
// TODO(dalesat): Support a different compressed output type by
// transcoding.
}
}
// Find the best LPCM output type.
const std::unique_ptr<StreamTypeSet>* best =
FindBestLpcm(in_type, out_type_sets);
if (best == nullptr) {
// No candidates found.
*out_type = nullptr;
return AddResult::kFailed;
}
FXL_DCHECK((*best)->medium() == StreamType::Medium::kAudio);
FXL_DCHECK((*best)->IncludesEncoding(StreamType::kAudioEncodingLpcm));
// Need to decode. Create a decoder and go from there.
std::shared_ptr<Decoder> decoder;
Result result = Decoder::Create(in_type, &decoder);
if (result != Result::kOk) {
// No decoder found.
*out_type = nullptr;
return AddResult::kFailed;
}
*output = graph->ConnectOutputToNode(*output, graph->Add(decoder)).output();
*out_type = decoder->output_stream_type();
return AddResult::kProgressed;
}
// Attempts to add transforms to the pipeline given an input compressed video
// stream type with (in_type) and the set of output types we need to convert to
// (out_type_sets). If the call succeeds, *out_type is set to the new output
// type. Otherwise, *out_type is set to nullptr.
AddResult AddTransformsForCompressedVideo(
const VideoStreamType& in_type,
const std::vector<std::unique_ptr<StreamTypeSet>>& out_type_sets,
Graph* graph,
OutputRef* output,
std::unique_ptr<StreamType>* out_type) {
FXL_DCHECK(out_type);
FXL_DCHECK(graph);
// TODO(dalesat): See if we already have a matching video type.
// Need to decode. Create a decoder and go from there.
std::shared_ptr<Decoder> decoder;
Result result = Decoder::Create(in_type, &decoder);
if (result != Result::kOk) {
// No decoder found.
*out_type = nullptr;
return AddResult::kFailed;
}
*output = graph->ConnectOutputToNode(*output, graph->Add(decoder)).output();
*out_type = decoder->output_stream_type();
return AddResult::kProgressed;
}
// Attempts to add transforms to the pipeline given an input LPCM stream type
// (in_type) and the output lpcm stream type set for the type we need to
// convert to (out_type_set). If the call succeeds, *out_type is set to the new
// output type. Otherwise, *out_type is set to nullptr.
AddResult AddTransformsForLpcm(const AudioStreamType& in_type,
const AudioStreamTypeSet& out_type_set,
Graph* graph,
OutputRef* output,
std::unique_ptr<StreamType>* out_type) {
FXL_DCHECK(graph);
FXL_DCHECK(out_type);
// TODO(dalesat): Room for more intelligence here wrt transform ordering and
// transforms that handle more than one conversion.
if (in_type.sample_format() != out_type_set.sample_format() &&
out_type_set.sample_format() != AudioStreamType::SampleFormat::kAny) {
*output = graph
->ConnectOutputToNode(
*output, graph->Add(LpcmReformatter::Create(
in_type, out_type_set.sample_format())))
.output();
}
if (!out_type_set.channels().contains(in_type.channels())) {
// TODO(dalesat): Insert mixdown/up transform.
FXL_DCHECK(false) << "conversion requires mixdown/up - not supported";
*out_type = nullptr;
return AddResult::kFailed;
}
if (!out_type_set.frames_per_second().contains(in_type.frames_per_second())) {
// TODO(dalesat): Insert resampler.
FXL_DCHECK(false) << "conversion requires resampling - not supported";
*out_type = nullptr;
return AddResult::kFailed;
}
// Build the resulting media type.
*out_type = AudioStreamType::Create(
StreamType::kAudioEncodingLpcm, nullptr,
out_type_set.sample_format() == AudioStreamType::SampleFormat::kAny
? in_type.sample_format()
: out_type_set.sample_format(),
in_type.channels(), in_type.frames_per_second());
return AddResult::kFinished;
}
// Attempts to add transforms to the pipeline given an input audio stream type
// witn lpcm encoding (in_type) and the set of output types we need to convert
// to (out_type_sets). If the call succeeds, *out_type is set to the new
// output type. Otherwise, *out_type is set to nullptr.
AddResult AddTransformsForLpcm(
const AudioStreamType& in_type,
const std::vector<std::unique_ptr<StreamTypeSet>>& out_type_sets,
Graph* graph,
OutputRef* output,
std::unique_ptr<StreamType>* out_type) {
FXL_DCHECK(graph);
FXL_DCHECK(out_type);
const std::unique_ptr<StreamTypeSet>* best =
FindBestLpcm(in_type, out_type_sets);
if (best == nullptr) {
// TODO(dalesat): Support a compressed output type by encoding.
FXL_DCHECK(false) << "conversion using encoder not supported";
*out_type = nullptr;
return AddResult::kFailed;
}
FXL_DCHECK((*best)->medium() == StreamType::Medium::kAudio);
return AddTransformsForLpcm(in_type, *(*best)->audio(), graph, output,
out_type);
}
// Attempts to add transforms to the pipeline given an input media type of any
// medium and encoding (in_type) and the set of output types we need to
// convert to (out_type_sets). If the call succeeds, *out_type is set to the new
// output type. Otherwise, *out_type is set to nullptr.
AddResult AddTransforms(
const StreamType& in_type,
const std::vector<std::unique_ptr<StreamTypeSet>>& out_type_sets,
Graph* graph,
OutputRef* output,
std::unique_ptr<StreamType>* out_type) {
FXL_DCHECK(graph);
FXL_DCHECK(out_type);
switch (in_type.medium()) {
case StreamType::Medium::kAudio:
if (in_type.encoding() == StreamType::kAudioEncodingLpcm) {
return AddTransformsForLpcm(*in_type.audio(), out_type_sets, graph,
output, out_type);
} else {
return AddTransformsForCompressedAudio(*in_type.audio(), out_type_sets,
graph, output, out_type);
}
case StreamType::Medium::kVideo:
if (in_type.encoding() == StreamType::kVideoEncodingUncompressed) {
*out_type = in_type.Clone();
return AddResult::kFinished;
} else {
return AddTransformsForCompressedVideo(*in_type.video(), out_type_sets,
graph, output, out_type);
}
default:
FXL_DCHECK(false) << "conversion not supported for medium"
<< in_type.medium();
*out_type = nullptr;
return AddResult::kFailed;
}
}
} // namespace
bool BuildConversionPipeline(
const StreamType& in_type,
const std::vector<std::unique_ptr<StreamTypeSet>>& out_type_sets,
Graph* graph,
OutputRef* output,
std::unique_ptr<StreamType>* out_type) {
FXL_DCHECK(graph);
FXL_DCHECK(output);
FXL_DCHECK(out_type);
OutputRef out = *output;
const StreamType* type_to_convert = &in_type;
std::unique_ptr<StreamType> converted_type;
while (true) {
switch (AddTransforms(*type_to_convert, out_type_sets, graph, &out,
&converted_type)) {
case AddResult::kFailed:
// Failed to find a suitable conversion. Return the pipeline to its
// original state.
graph->RemoveNodesConnectedToOutput(*output);
*out_type = nullptr;
return false;
case AddResult::kProgressed:
// Made progress. Continue.
break;
case AddResult::kFinished:
// No further conversion required.
*output = out;
*out_type = std::move(converted_type);
return true;
}
type_to_convert = converted_type.get();
}
}
} // namespace media