| // 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 <audio-proto-utils/format-utils.h> |
| #include <fuchsia/scheduler/cpp/fidl.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/zx/channel.h> |
| |
| #include "src/lib/fxl/logging.h" |
| #include "src/media/audio/audio_core/driver_utils.h" |
| |
| namespace media::audio { |
| |
| 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) { |
| 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 (!driver_utils::AudioSampleFormatToDriverSampleFormat( |
| *sample_format_inout, &pref_sample_format)) { |
| FXL_LOG(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 MTWN-54 |
| 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 { |
| FXL_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. |
| FXL_LOG(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 { |
| FXL_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 { |
| FXL_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) { |
| FXL_LOG(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. |
| |
| FXL_DCHECK(score > 0); |
| FXL_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 = |
| driver_utils::DriverSampleFormatToAudioSampleFormat(best_sample_format, |
| sample_format_inout); |
| FXL_DCHECK(convert_res); |
| |
| *channels_inout = best_channels; |
| *frames_per_second_inout = best_frame_rate; |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t AcquireHighPriorityProfile(zx::profile* profile) { |
| // 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) { |
| FXL_LOG(ERROR) << "Failed to create channel, res=" << res; |
| return res; |
| } |
| |
| res = fdio_service_connect( |
| (std::string("/svc/") + fuchsia::scheduler::ProfileProvider::Name_) |
| .c_str(), |
| ch0.get()); |
| if (res != ZX_OK) { |
| FXL_LOG(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.GetProfile(24 /* HIGH_PRIORITY */, |
| "src/media/audio/audio_core", &fidl_status, |
| &res_profile); |
| if (res != ZX_OK) { |
| FXL_LOG(ERROR) << "Failed to create profile, res=" << res; |
| return res; |
| } |
| if (fidl_status != ZX_OK) { |
| FXL_LOG(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); |
| } |
| |
| } // namespace media::audio |