blob: b5b4f365c2a79a5a5d6766615f0a6de3e50325ef [file] [log] [blame]
// Copyright 2017 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 "src/media/audio/audio_core/shared/select_best_format.h"
#include <lib/fdio/directory.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <cstdlib>
#include <audio-proto-utils/format-utils.h>
#include "src/media/audio/lib/format/driver_format.h"
namespace media::audio {
namespace {
bool IsSampleFormatInSupported(
fuchsia::hardware::audio::SampleFormat sample_format, uint8_t bytes_per_sample,
const fuchsia::hardware::audio::PcmSupportedFormats& supported_formats) {
auto& sf = supported_formats.sample_formats();
if (std::find(sf.begin(), sf.end(), sample_format) == sf.end()) {
return false;
}
auto& bps = supported_formats.bytes_per_sample();
if (std::find(bps.begin(), bps.end(), bytes_per_sample) == bps.end()) {
return false;
}
return true;
}
bool IsNumberOfChannelsInSupported(uint32_t number_of_channels,
const fuchsia::hardware::audio::PcmSupportedFormats& format) {
for (auto& channel_set : format.channel_sets()) {
if (channel_set.attributes().size() == number_of_channels) {
return true;
}
}
return false;
}
bool IsRateInSupported(uint32_t frame_rate,
const fuchsia::hardware::audio::PcmSupportedFormats& format) {
for (auto rate : format.frame_rates()) {
if (rate == frame_rate) {
return true;
}
}
return false;
}
} // namespace
bool IsFormatInSupported(
const fuchsia::media::AudioStreamType& stream_type,
const std::vector<fuchsia::hardware::audio::PcmSupportedFormats>& supported_formats) {
DriverSampleFormat driver_format = {};
if (!AudioSampleFormatToDriverSampleFormat(stream_type.sample_format, &driver_format)) {
return false;
}
for (const auto& format : supported_formats) {
if (IsSampleFormatInSupported(driver_format.sample_format, driver_format.bytes_per_sample,
format) &&
IsNumberOfChannelsInSupported(stream_type.channels, format) &&
IsRateInSupported(stream_type.frames_per_second, format)) {
return true;
}
}
return false;
}
zx_status_t SelectBestFormat(const std::vector<fuchsia::hardware::audio::PcmSupportedFormats>& fmts,
uint32_t* frames_per_second_inout, uint32_t* channels_inout,
fuchsia::media::AudioSampleFormat* sample_format_inout) {
TRACE_DURATION("audio", "SelectBestFormat");
if ((frames_per_second_inout == nullptr) || (channels_inout == nullptr) ||
(sample_format_inout == nullptr)) {
return ZX_ERR_INVALID_ARGS;
}
uint32_t pref_frame_rate = *frames_per_second_inout;
uint32_t pref_channels = *channels_inout;
DriverSampleFormat pref_sample_format = {};
// Only valid pref_sample_formats are: unsigned-8, signed-16, signed-24in32 or float-32.
if (!AudioSampleFormatToDriverSampleFormat(*sample_format_inout, &pref_sample_format)) {
FX_LOGS(ERROR) << "Failed to convert FIDL sample format ("
<< static_cast<uint32_t>(*sample_format_inout) << ") to driver sample format.";
return ZX_ERR_INVALID_ARGS;
}
uint32_t best_frame_rate = 0;
uint32_t best_channels = 0;
DriverSampleFormat best_sample_format = {};
uint32_t best_score = 0;
uint32_t best_frame_rate_delta = std::numeric_limits<uint32_t>::max();
for (const auto& format : fmts) {
// Start by scoring our sample format. Direct match gets 5 points. Otherwise, supported sample
// formats are scored by decreasing preference.
DriverSampleFormat this_sample_format;
int sample_format_score = 0;
if (IsSampleFormatInSupported(pref_sample_format.sample_format,
pref_sample_format.bytes_per_sample, format)) {
this_sample_format = pref_sample_format;
sample_format_score = 5;
} else if (IsSampleFormatInSupported(fuchsia::hardware::audio::SampleFormat::PCM_FLOAT, 4,
format)) {
this_sample_format = {fuchsia::hardware::audio::SampleFormat::PCM_FLOAT, 4, 32};
sample_format_score = 4;
} else if (IsSampleFormatInSupported(fuchsia::hardware::audio::SampleFormat::PCM_SIGNED, 4,
format)) {
this_sample_format = {fuchsia::hardware::audio::SampleFormat::PCM_SIGNED, 4, 24};
sample_format_score = 3;
} else if (IsSampleFormatInSupported(fuchsia::hardware::audio::SampleFormat::PCM_SIGNED, 2,
format)) {
this_sample_format = {fuchsia::hardware::audio::SampleFormat::PCM_SIGNED, 2, 16};
sample_format_score = 2;
} else if (IsSampleFormatInSupported(fuchsia::hardware::audio::SampleFormat::PCM_UNSIGNED, 1,
format)) {
this_sample_format = {fuchsia::hardware::audio::SampleFormat::PCM_UNSIGNED, 1, 8};
sample_format_score = 1;
}
// Next consider the supported channel counts. 3 points for matching the requested channel
// count. Otherwise, default to stereo (if supported) and score 2 points. Failing that, just
// pick the top end of the supported channel range and score 1 point.
uint32_t this_channels = 0;
int channel_count_score = 0;
if (IsNumberOfChannelsInSupported(pref_channels, format)) {
this_channels = pref_channels;
channel_count_score = 3;
} else if (IsNumberOfChannelsInSupported(2, format)) {
this_channels = 2;
channel_count_score = 2;
} else {
auto compare = [](const fuchsia::hardware::audio::ChannelSet& left,
const fuchsia::hardware::audio::ChannelSet& right) {
return left.attributes().size() < right.attributes().size();
};
auto channel_set =
std::max_element(format.channel_sets().begin(), format.channel_sets().end(), compare);
this_channels = static_cast<uint32_t>(channel_set->attributes().size());
channel_count_score = 1;
}
// Next score based on supported frame rates. Score 3 points for a match, 2 points if we have to
// scale up to the nearest supported rate, or 1 point if we have to scale down.
uint32_t this_frame_rate = 0;
uint32_t frame_rate_delta = std::numeric_limits<uint32_t>::max();
int frame_rate_score = 0;
if (IsRateInSupported(pref_frame_rate, format)) {
this_frame_rate = pref_frame_rate;
channel_count_score = 3;
frame_rate_delta = 0;
} else {
uint32_t delta = std::numeric_limits<uint32_t>::max();
for (auto& i : format.frame_rates()) {
auto d = static_cast<uint32_t>(
std::abs(static_cast<int32_t>(i) - static_cast<int32_t>(pref_frame_rate)));
if (d < delta) {
delta = d;
this_frame_rate = i;
}
}
if (pref_frame_rate < this_frame_rate) {
frame_rate_delta = this_frame_rate - pref_frame_rate;
channel_count_score = 2;
} else {
frame_rate_delta = pref_frame_rate - this_frame_rate;
channel_count_score = 1;
}
}
// OK, we have computed the best option supported by this frame rate range. Weight the score,
// and if it is better then any of our previous best score, replace our previous best with this.
//
// TODO(https://fxbug.dev/42070759): reconsider this scoring formula
uint32_t score;
score = (sample_format_score * 100) // format is the most important.
+ (channel_count_score * 10) // channel count comes second.
+ frame_rate_score; // frame rate is the least important.
FX_DCHECK(score > 0);
// If this score is better than the current best score, or this score ties the current best
// score but the frame rate distance is less, then this is the new best format.
if ((score > best_score) ||
((score == best_score) && (frame_rate_delta < best_frame_rate_delta))) {
best_frame_rate = this_frame_rate;
best_frame_rate_delta = frame_rate_delta;
best_channels = this_channels;
best_sample_format = this_sample_format;
best_score = score;
}
}
// If our score is still zero, then there must have been absolutely no supported formats in the
// set provided by the driver.
if (!best_score) {
return ZX_ERR_NOT_SUPPORTED;
}
bool convert_res = DriverSampleFormatToAudioSampleFormat(best_sample_format, sample_format_inout);
FX_DCHECK(convert_res);
*channels_inout = best_channels;
*frames_per_second_inout = best_frame_rate;
return ZX_OK;
}
zx::result<media_audio::Format> SelectBestFormat(
const std::vector<fuchsia_audio_device::PcmFormatSet>& supported_formats,
const media_audio::Format& pref) {
// Convert types.
std::vector<fuchsia::hardware::audio::PcmSupportedFormats> hardware_supported_formats;
for (auto& fmt : supported_formats) {
fuchsia::hardware::audio::PcmSupportedFormats hw_fmt;
if (fmt.channel_sets()) {
for (auto& chan_set : *fmt.channel_sets()) {
fuchsia::hardware::audio::ChannelSet hw_chan_set;
if (chan_set.attributes()) {
std::vector<fuchsia::hardware::audio::ChannelAttributes> hw_attrs;
for (auto& attr : *chan_set.attributes()) {
fuchsia::hardware::audio::ChannelAttributes hw_attr;
if (attr.min_frequency()) {
hw_attr.set_min_frequency(*attr.min_frequency());
}
if (attr.max_frequency()) {
hw_attr.set_max_frequency(*attr.max_frequency());
}
hw_attrs.push_back(std::move(hw_attr));
}
hw_chan_set.set_attributes(std::move(hw_attrs));
}
hw_fmt.mutable_channel_sets()->push_back(std::move(hw_chan_set));
}
}
if (fmt.sample_types()) {
for (auto sample_type : *fmt.sample_types()) {
using HardwareSampleFormat = fuchsia::hardware::audio::SampleFormat;
switch (sample_type) {
case fuchsia_audio::SampleType::kUint8:
hw_fmt.mutable_sample_formats()->push_back(HardwareSampleFormat::PCM_UNSIGNED);
hw_fmt.mutable_bytes_per_sample()->push_back(1);
break;
case fuchsia_audio::SampleType::kInt16:
hw_fmt.mutable_sample_formats()->push_back(HardwareSampleFormat::PCM_SIGNED);
hw_fmt.mutable_bytes_per_sample()->push_back(2);
break;
case fuchsia_audio::SampleType::kInt32:
hw_fmt.mutable_sample_formats()->push_back(HardwareSampleFormat::PCM_SIGNED);
hw_fmt.mutable_bytes_per_sample()->push_back(4);
break;
case fuchsia_audio::SampleType::kFloat32:
hw_fmt.mutable_sample_formats()->push_back(HardwareSampleFormat::PCM_FLOAT);
hw_fmt.mutable_bytes_per_sample()->push_back(4);
break;
default:
FX_LOGS(WARNING) << "sample type '" << fidl::ToUnderlying(sample_type)
<< "' not supported by HW";
continue;
}
}
}
if (fmt.frame_rates()) {
for (auto fps : *fmt.frame_rates()) {
hw_fmt.mutable_frame_rates()->push_back(fps);
}
}
hardware_supported_formats.push_back(std::move(hw_fmt));
}
// Call the shared impl.
auto legacy_pref = pref.ToLegacyMediaWireFidl();
auto status = SelectBestFormat(
hardware_supported_formats, &legacy_pref.frames_per_second, &legacy_pref.channels,
// This cast is safe because the two types are the same enums: the source is a wire type and
// the dest is an HLCPP type.
reinterpret_cast<fuchsia::media::AudioSampleFormat*>(&legacy_pref.sample_format));
if (status != ZX_OK) {
return zx::error(status);
}
return zx::ok(media_audio::Format::CreateLegacyOrDie(legacy_pref));
}
} // namespace media::audio