| // 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 |