blob: 2fd06fdd457b3465367b5e500fb498a0c2710a31 [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/utils.h"
#include <fuchsia/scheduler/cpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <lib/zx/channel.h>
#include <cstdlib>
#include <audio-proto-utils/format-utils.h>
#include "src/media/audio/audio_core/threading_model.h"
#include "src/media/audio/lib/format/driver_format.h"
namespace media::audio {
bool IsSampleFormatInSupported(
fuchsia::hardware::audio::SampleFormat sample_format, uint8_t bytes_per_sample,
uint8_t valid_bits_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;
}
auto& vbps = supported_formats.valid_bits_per_sample;
if (std::find(vbps.begin(), vbps.end(), valid_bits_per_sample) == vbps.end()) {
return false;
}
return true;
}
bool IsNumberOfChannelsInSupported(uint32_t number_of_channels,
const fuchsia::hardware::audio::PcmSupportedFormats& format) {
for (auto channels : format.number_of_channels) {
if (channels == 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;
}
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;
}
// Is there a match for any given suppored format where we find sample format, number of channels
// and rate.
for (const auto& format : supported_formats) {
if (IsSampleFormatInSupported(driver_format.sample_format, driver_format.bytes_per_sample,
driver_format.valid_bits_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. Right now, the audio core supports 8-bit unsigned, 16-bit
// signed, 24-bit-in-32 signed and 32-bit float. If this sample format range does not support
// any of these, just skip it for now. Otherwise, 5 points if you match the requested format, 4
// for signed-24, 3 for signed-16, 2 for float-32, or 1 for unsigned-8.
DriverSampleFormat this_sample_format;
int sample_format_score = 0;
// Check for direct match.
if (IsSampleFormatInSupported(pref_sample_format.sample_format,
pref_sample_format.bytes_per_sample,
pref_sample_format.valid_bits_per_sample, format)) {
this_sample_format = pref_sample_format;
sample_format_score = 5;
} else if (IsSampleFormatInSupported(fuchsia::hardware::audio::SampleFormat::PCM_SIGNED, 4, 24,
format)) {
this_sample_format = {fuchsia::hardware::audio::SampleFormat::PCM_SIGNED, 4, 24};
sample_format_score = 4;
} else if (IsSampleFormatInSupported(fuchsia::hardware::audio::SampleFormat::PCM_SIGNED, 2, 16,
format)) {
this_sample_format = {fuchsia::hardware::audio::SampleFormat::PCM_SIGNED, 2, 16};
sample_format_score = 3;
} else if (IsSampleFormatInSupported(fuchsia::hardware::audio::SampleFormat::PCM_FLOAT, 4, 32,
format)) {
this_sample_format = {fuchsia::hardware::audio::SampleFormat::PCM_FLOAT, 4, 32};
sample_format_score = 2;
} else if (IsSampleFormatInSupported(fuchsia::hardware::audio::SampleFormat::PCM_UNSIGNED, 1, 8,
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 {
this_channels =
*std::max_element(format.number_of_channels.begin(), format.number_of_channels.end());
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) {
if (std::abs((long)i - (long)pref_frame_rate) < delta) {
delta = std::abs((long)i - (long)pref_frame_rate);
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 it if it better then any of our previous best score, replace our previous best with this.
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 be absolutely no supported formats in the set
// provided by the driver.
if (!best_score) {
return ZX_ERR_NOT_SUPPORTED;
}
__UNUSED 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_status_t SelectBestFormat(const std::vector<audio_stream_format_range_t>& 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;
audio_sample_format_t pref_sample_format;
if (!AudioSampleFormatToDriverSampleFormat(*sample_format_inout, &pref_sample_format)) {
FX_LOGS(WARNING) << "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;
uint32_t best_channels;
audio_sample_format_t best_sample_format;
uint32_t best_score = 0;
uint32_t best_frame_rate_delta = std::numeric_limits<uint32_t>::max();
constexpr uint32_t U8_FMT = AUDIO_SAMPLE_FORMAT_8BIT | AUDIO_SAMPLE_FORMAT_FLAG_UNSIGNED;
constexpr uint32_t S16_FMT = AUDIO_SAMPLE_FORMAT_16BIT;
constexpr uint32_t S24_FMT = AUDIO_SAMPLE_FORMAT_24BIT_IN32;
constexpr uint32_t F32_FMT = AUDIO_SAMPLE_FORMAT_32BIT_FLOAT;
// Users should only ask for unsigned-8, signed-16, signed-24in32 or float-32. If they ask for
// anything else, change their preference to signed-16.
//
// TODO(johngro) : clean this up as part of fixing fxbug.dev/13341
if ((pref_sample_format & AUDIO_SAMPLE_FORMAT_FLAG_INVERT_ENDIAN) ||
(((pref_sample_format & U8_FMT) != U8_FMT) && ((pref_sample_format & S16_FMT) != S16_FMT) &&
((pref_sample_format & S24_FMT) != S24_FMT) &&
((pref_sample_format & F32_FMT) != F32_FMT))) {
pref_sample_format = AUDIO_SAMPLE_FORMAT_16BIT;
}
for (const auto& range : fmts) {
// Start by scoring our sample format. Right now, the audio core supports 8-bit unsigned, 16-bit
// signed, 24-bit-in-32 signed and 32-bit float. If this sample format range does not support
// any of these, just skip it for now. Otherwise, 5 points if you match the requested format, 4
// for signed-24, 3 for signed-16, 2 for float-32, or 1 for unsigned-8.
audio_sample_format_t this_sample_format;
int sample_format_score;
bool supports_u8 = (range.sample_formats & U8_FMT) == U8_FMT;
bool supports_s16 = (range.sample_formats & S16_FMT) == S16_FMT;
bool supports_s24 = (range.sample_formats & S24_FMT) == S24_FMT;
bool supports_f32 = (range.sample_formats & F32_FMT) == F32_FMT;
if ((range.sample_formats & AUDIO_SAMPLE_FORMAT_FLAG_INVERT_ENDIAN) ||
(!supports_u8 && !supports_s16 && !supports_s24 && !supports_f32)) {
continue; // Otherwise skip: this isn't a sample container we understand.
}
if ((pref_sample_format & range.sample_formats) == pref_sample_format) {
// Direct match.
this_sample_format = pref_sample_format;
sample_format_score = 5;
} else if (supports_s24) {
this_sample_format = AUDIO_SAMPLE_FORMAT_24BIT_IN32;
sample_format_score = 4;
} else if (supports_s16) {
this_sample_format = AUDIO_SAMPLE_FORMAT_16BIT;
sample_format_score = 3;
} else if (supports_f32) {
this_sample_format = AUDIO_SAMPLE_FORMAT_32BIT_FLOAT;
sample_format_score = 2;
} else {
FX_DCHECK(supports_u8);
this_sample_format = static_cast<audio_sample_format_t>(U8_FMT);
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;
int channel_count_score;
if ((pref_channels >= range.min_channels) && (pref_channels <= range.max_channels)) {
this_channels = pref_channels;
channel_count_score = 3;
} else if ((2 >= range.min_channels) && (2 <= range.max_channels)) {
this_channels = 2;
channel_count_score = 2;
} else {
this_channels = range.max_channels;
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.
//
if (range.min_frames_per_second > range.max_frames_per_second) {
// Skip this frame rate range entirely if it is empty.
FX_LOGS(INFO) << "Skipping empty frame rate range [" << range.min_frames_per_second << ", "
<< range.max_frames_per_second
<< "] while searching for best format in driver list.";
continue;
}
uint32_t this_frame_rate = 0;
uint32_t frame_rate_delta = std::numeric_limits<uint32_t>::max();
int frame_rate_score = 0;
if (range.flags & ASF_RANGE_FLAG_FPS_CONTINUOUS) {
// This is a continuous sample rate range. If we are within the range, thats a match.
// Otherwise move up/down as needed to match the min/max of the range as appropriate.
if ((pref_frame_rate >= range.min_frames_per_second) &&
(pref_frame_rate <= range.max_frames_per_second)) {
this_frame_rate = pref_frame_rate;
frame_rate_score = 3;
frame_rate_delta = 0;
} else if (pref_frame_rate < range.min_frames_per_second) {
this_frame_rate = range.min_frames_per_second;
frame_rate_score = 2;
frame_rate_delta = range.min_frames_per_second - pref_frame_rate;
} else {
FX_DCHECK(pref_frame_rate > range.max_frames_per_second);
this_frame_rate = range.max_frames_per_second;
frame_rate_score = 1;
frame_rate_delta = pref_frame_rate - range.max_frames_per_second;
}
} else {
// This is a discrete sample rate range. Use the frame rate enumerator utility class to
// evaluate each of the possible frame rates.
for (const uint32_t rate : ::audio::utils::FrameRateEnumerator(range)) {
if (pref_frame_rate == rate) {
// We matched our preference. No need to keep searching.
this_frame_rate = rate;
frame_rate_score = 3;
frame_rate_delta = 0;
break;
}
if (pref_frame_rate < rate) {
// Scaling up; 2 points. If this better than what we were doing before, just choose it.
// If we were already going to scale up, pick the frame rate which is closer to our
// preference. Otherwise, do nothing.
if ((frame_rate_score < 2) || ((frame_rate_score == 2) && (rate < this_frame_rate))) {
this_frame_rate = rate;
frame_rate_score = 2;
frame_rate_delta = rate - pref_frame_rate;
}
} else {
FX_DCHECK(pref_frame_rate > rate);
// Scaling down; 1 point. If this better than what we were doing before, just choose it.
// If we were already going to scale down, pick the frame rate which is closer to our
// preference. Otherwise, do nothing.
if ((frame_rate_score < 1) || ((frame_rate_score == 1) && (rate > this_frame_rate))) {
this_frame_rate = rate;
frame_rate_score = 1;
frame_rate_delta = pref_frame_rate - rate;
}
}
}
}
// If our frame rate score is still zero, it means that we were given a discrete frame range by
// a driver which was completely empty (even though min was <= max as it should be) Debug log a
// warning, then skip the range entirely.
if (frame_rate_score == 0) {
FX_LOGS(INFO) << "Skipping empty discrete frame rate range [" << range.min_frames_per_second
<< ", " << range.max_frames_per_second << "] (flags " << range.flags
<< ") while searching for best format";
continue;
}
// OK, we have computed the best option supported by this frame rate range. Weight the score,
// and it if it better then any of our previous best score, replace our previous best with this.
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);
FX_DCHECK(::audio::utils::FormatIsCompatible(this_frame_rate, this_channels, this_sample_format,
range));
// 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 be absolutely no supported formats in the set
// provided by the driver.
if (!best_score) {
return ZX_ERR_NOT_SUPPORTED;
}
__UNUSED 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_status_t AcquireHighPriorityProfile(zx::profile* profile) {
TRACE_DURATION("audio", "AcquireHighPriorityProfile");
// Use threadsafe static initialization to get our one-and-only copy of this profile object. Each
// subsequent call will return a duplicate of that profile handle to ensure sharing of thread
// pools.
static zx::profile high_priority_profile;
static zx_status_t initial_status = [](zx::profile* profile) {
zx::channel ch0, ch1;
zx_status_t res = zx::channel::create(0u, &ch0, &ch1);
if (res != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create channel, res=" << res;
return res;
}
res = fdio_service_connect(
(std::string("/svc/") + fuchsia::scheduler::ProfileProvider::Name_).c_str(), ch0.release());
if (res != ZX_OK) {
FX_LOGS(ERROR) << "Failed to connect to ProfileProvider, res=" << res;
return res;
}
fuchsia::scheduler::ProfileProvider_SyncProxy provider(std::move(ch1));
zx_status_t fidl_status;
zx::profile res_profile;
res = provider.GetDeadlineProfile(ThreadingModel::kMixProfileCapacity.get(),
ThreadingModel::kMixProfileDeadline.get(),
ThreadingModel::kMixProfilePeriod.get(),
"src/media/audio/audio_core", &fidl_status, &res_profile);
if (res != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create profile, res=" << res;
return res;
}
if (fidl_status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create profile, fidl_status=" << fidl_status;
return fidl_status;
}
*profile = std::move(res_profile);
return ZX_OK;
}(&high_priority_profile);
// If the initial acquisition of the profile failed, return that status.
if (initial_status != ZX_OK)
return initial_status;
// Otherwise, dupe this handle and return it.
return high_priority_profile.duplicate(ZX_RIGHT_SAME_RIGHTS, profile);
}
void AcquireRelativePriorityProfile(uint32_t priority, sys::ComponentContext* context,
fit::function<void(zx_status_t, zx::profile)> callback) {
auto nonce = TRACE_NONCE();
TRACE_DURATION("audio", "AcquireRelativePriorityProfile");
TRACE_FLOW_BEGIN("audio", "GetProfile", nonce);
auto profile_provider = context->svc()->Connect<fuchsia::scheduler::ProfileProvider>();
profile_provider->GetProfile(
priority, "src/media/audio/audio_core/audio_core_impl",
// Note we move the FIDL ptr into the closure to ensure we keep the channel open until we
// receive the callback, otherwise it will be impossible to get a response.
[profile_provider = std::move(profile_provider), callback = std::move(callback), nonce](
zx_status_t status, zx::profile profile) {
TRACE_DURATION("audio", "GetProfile callback");
TRACE_FLOW_END("audio", "GetProfile", nonce);
if (status == ZX_OK) {
callback(status, std::move(profile));
} else {
callback(status, zx::profile());
}
});
}
void AcquireAudioCoreImplProfile(sys::ComponentContext* context,
fit::function<void(zx_status_t, zx::profile)> callback) {
AcquireRelativePriorityProfile(/* HIGH_PRIORITY in zircon */ 24, context, std::move(callback));
}
} // namespace media::audio