audio: import alsa_utils

* system/media/ at android-16.0.0_r1.
This commit is contained in:
Konsta
2025-10-22 17:23:45 +03:00
parent 7d6df38051
commit 487b7689f6
9 changed files with 1689 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package {
// http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// the below license kinds from "system_media_license":
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["system_media_license"],
}
cc_defaults {
name: "libalsautils_defaults",
vendor_available: true,
srcs: [
"alsa_device_profile.c",
"alsa_device_proxy.c",
"alsa_format.c",
"alsa_logging.c",
],
export_include_dirs: ["include"],
header_libs: [
"libaudio_system_headers",
],
export_header_lib_headers: [
"libaudio_system_headers",
],
shared_libs: [
"libaudioutils",
"libcutils",
"liblog",
],
cflags: [
"-Wall",
"-Werror",
"-Wno-unused-parameter",
],
}
cc_library {
name: "libalsautils",
defaults: ["libalsautils_defaults"],
shared_libs: [
"libtinyalsa",
],
}
cc_library {
name: "libalsautilsv2",
defaults: ["libalsautils_defaults"],
shared_libs: [
"libtinyalsav2",
],
}

View File

@@ -0,0 +1,748 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "alsa_device_profile"
/*#define LOG_NDEBUG 0*/
/*#define LOG_PCM_PARAMS 0*/
#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdlib.h>
#include <cutils/properties.h>
#include <log/log.h>
#include "include/alsa_device_profile.h"
#include "include/alsa_format.h"
#include "include/alsa_logging.h"
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#define PERIOD_DURATION_US (5 * 1000)
#define DEFAULT_PERIOD_SIZE 1024
static const char * const format_string_map[] = {
"AUDIO_FORMAT_PCM_16_BIT", /* "PCM_FORMAT_S16_LE", */
"AUDIO_FORMAT_PCM_32_BIT", /* "PCM_FORMAT_S32_LE", */
"AUDIO_FORMAT_PCM_8_BIT", /* "PCM_FORMAT_S8", */
"AUDIO_FORMAT_PCM_8_24_BIT", /* "PCM_FORMAT_S24_LE", */
"AUDIO_FORMAT_PCM_24_BIT_PACKED"/* "PCM_FORMAT_S24_3LE" */
};
extern int8_t const pcm_format_value_map[50];
/* Sort these in terms of preference (best first).
192 kHz is not first because it requires significant resources for possibly worse
quality and driver instability (depends on device).
The order here determines the default sample rate for the device.
AudioPolicyManager may not respect this ordering when picking sample rates.
Update MAX_PROFILE_SAMPLE_RATES after changing the array size.
TODO: remove 32000, 22050, 12000, 11025? Each sample rate check
requires opening the device which may cause pops. */
static const unsigned std_sample_rates[] =
{96000, 88200, 192000, 176400, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000};
static void profile_reset(alsa_device_profile* profile)
{
profile->card = profile->device = -1;
profile->extra_latency_ms = 0;
/* terminate the attribute arrays with invalid values */
profile->formats[0] = PCM_FORMAT_INVALID;
profile->sample_rates[0] = 0;
profile->channel_counts[0] = 0;
profile->min_period_size = profile->max_period_size = 0;
profile->min_channel_count = profile->max_channel_count = DEFAULT_CHANNEL_COUNT;
profile->is_valid = false;
}
void profile_init(alsa_device_profile* profile, int direction)
{
profile->direction = direction;
profile_reset(profile);
}
bool profile_is_initialized(const alsa_device_profile* profile)
{
return profile->card >= 0 && profile->device >= 0;
}
bool profile_is_valid(const alsa_device_profile* profile) {
return profile->is_valid;
}
bool profile_is_cached_for(const alsa_device_profile* profile, int card, int device) {
return card == profile->card && device == profile->device;
}
void profile_decache(alsa_device_profile* profile) {
profile_reset(profile);
}
/*
* Returns the supplied value rounded up to the next even multiple of 16
*/
static unsigned int round_to_16_mult(unsigned int size)
{
return (size + 15) & ~15; /* 0xFFFFFFF0; */
}
/*
* Returns the system defined minimum period size based on the supplied sample rate.
*/
unsigned profile_calc_min_period_size(const alsa_device_profile* profile, unsigned sample_rate)
{
ALOGV("profile_calc_min_period_size(%p, rate:%d)", profile, sample_rate);
if (profile == NULL) {
return DEFAULT_PERIOD_SIZE;
} else {
unsigned period_us = property_get_int32("ro.audio.usb.period_us", PERIOD_DURATION_US);
unsigned num_sample_frames = ((uint64_t)sample_rate * period_us) / 1000000;
if (num_sample_frames < profile->min_period_size) {
num_sample_frames = profile->min_period_size;
}
return round_to_16_mult(num_sample_frames);
}
}
unsigned int profile_get_period_size(const alsa_device_profile* profile, unsigned sample_rate)
{
unsigned int period_size = profile_calc_min_period_size(profile, sample_rate);
ALOGV("profile_get_period_size(rate:%d) = %d", sample_rate, period_size);
return period_size;
}
/*
* Sample Rate
*/
unsigned profile_get_default_sample_rate(const alsa_device_profile* profile)
{
/*
* This is probably a poor algorithm. The default sample rate should be the highest (within
* limits) rate that is available for both input and output. HOWEVER, the profile has only
* one or the other, so that will need to be done at a higher level, like in the HAL.
*/
/*
* TODO this won't be right in general. we should store a preferred rate as we are scanning.
* But right now it will return the highest rate, which may be correct.
*/
return profile_is_valid(profile) ? profile->sample_rates[0] : DEFAULT_SAMPLE_RATE;
}
unsigned profile_get_highest_sample_rate(const alsa_device_profile* profile) {
/* The hightest sample rate is always stored in the first element of sample_rates.
* Note that profile_reset() initiaizes the first element of samples_rates to 0
* Which is what we want to return if the profile had not been read anyway.
*/
return profile->sample_rates[0];
}
bool profile_is_sample_rate_valid(const alsa_device_profile* profile, unsigned rate)
{
if (profile_is_valid(profile)) {
size_t index;
for (index = 0; profile->sample_rates[index] != 0; index++) {
if (profile->sample_rates[index] == rate) {
return true;
}
}
return false;
} else {
ALOGW("**** PROFILE NOT VALID!");
return rate == DEFAULT_SAMPLE_RATE;
}
}
/*
* Format
*/
enum pcm_format profile_get_default_format(const alsa_device_profile* profile)
{
/*
* TODO this won't be right in general. we should store a preferred format as we are scanning.
*/
return profile_is_valid(profile) ? profile->formats[0] : DEFAULT_SAMPLE_FORMAT;
}
bool profile_is_format_valid(const alsa_device_profile* profile, enum pcm_format fmt) {
if (profile_is_valid(profile)) {
size_t index;
for (index = 0; profile->formats[index] != PCM_FORMAT_INVALID; index++) {
if (profile->formats[index] == fmt) {
return true;
}
}
return false;
} else {
return fmt == DEFAULT_SAMPLE_FORMAT;
}
}
/*
* Channels
*/
unsigned profile_get_default_channel_count(const alsa_device_profile* profile)
{
return profile_is_valid(profile) ? profile->channel_counts[0] : DEFAULT_CHANNEL_COUNT;
}
unsigned profile_get_closest_channel_count(const alsa_device_profile* profile, unsigned count)
{
if (profile_is_valid(profile)) {
if (count < profile->min_channel_count) {
return profile->min_channel_count;
} else if (count > profile->max_channel_count) {
return profile->max_channel_count;
} else {
return count;
}
} else {
return 0;
}
}
bool profile_is_channel_count_valid(const alsa_device_profile* profile, unsigned count)
{
if (profile_is_initialized(profile)) {
return count >= profile->min_channel_count && count <= profile->max_channel_count;
} else {
return count == DEFAULT_CHANNEL_COUNT;
}
}
static bool profile_test_sample_rate(const alsa_device_profile* profile, unsigned rate)
{
struct pcm_config config = profile->default_config;
config.rate = rate;
// This method tests whether a sample rate is supported by the USB device
// by attempting to open it.
//
// The profile default_config currently contains the minimum channel count.
// As some usb devices cannot sustain the sample rate across all its supported
// channel counts, we try the largest usable channel count. This is
// bounded by FCC_LIMIT.
//
// If config.channels > FCC_LIMIT then we still test it for sample rate compatibility.
// It is possible that the USB device does not support less than a certain number
// of channels, and that minimum number is > FCC_LIMIT. Then the default_config
// channels will be > FCC_LIMIT (and we still proceed with the test).
//
// For example, the FocusRite Scarlett 18i20 supports between 16 to 20 playback
// channels and between 14 to 18 capture channels.
// If FCC_LIMIT is 8, we still need to use and test 16 output channels for playback
// and 14 input channels for capture, as that will be the ALSA opening configuration.
// The Android USB audio HAL layer will automatically zero pad to accommodate the
// 16 playback or 14 capture channel configuration from the (up to FCC_LIMIT)
// channels delivered by AudioFlinger.
if (config.channels < FCC_LIMIT) {
config.channels = profile->max_channel_count;
if (config.channels > FCC_LIMIT) config.channels = FCC_LIMIT;
}
bool works = false; /* let's be pessimistic */
struct pcm * pcm = pcm_open(profile->card, profile->device,
profile->direction, &config);
if (pcm != NULL) {
works = pcm_is_ready(pcm);
pcm_close(pcm);
}
return works;
}
static unsigned profile_enum_sample_rates(alsa_device_profile* profile, unsigned min, unsigned max)
{
unsigned num_entries = 0;
unsigned index;
for (index = 0; index < ARRAY_SIZE(std_sample_rates) &&
num_entries < ARRAY_SIZE(profile->sample_rates) - 1;
index++) {
if (std_sample_rates[index] >= min && std_sample_rates[index] <= max
&& profile_test_sample_rate(profile, std_sample_rates[index])) {
profile->sample_rates[num_entries++] = std_sample_rates[index];
}
}
profile->sample_rates[num_entries] = 0; /* terminate */
return num_entries; /* return # of supported rates */
}
static unsigned profile_enum_sample_formats(alsa_device_profile* profile,
const struct pcm_mask * mask)
{
const int num_slots = ARRAY_SIZE(mask->bits);
const int bits_per_slot = sizeof(mask->bits[0]) * 8;
const int table_size = ARRAY_SIZE(pcm_format_value_map);
int slot_index, bit_index, table_index;
table_index = 0;
int num_written = 0;
for (slot_index = 0; slot_index < num_slots && table_index < table_size;
slot_index++) {
unsigned bit_mask = 1;
for (bit_index = 0;
bit_index < bits_per_slot && table_index < table_size;
bit_index++) {
if ((mask->bits[slot_index] & bit_mask) != 0) {
enum pcm_format format = pcm_format_value_map[table_index];
/* Never return invalid (unrecognized) or 8-bit */
if (format != PCM_FORMAT_INVALID && format != PCM_FORMAT_S8) {
profile->formats[num_written++] = format;
if (num_written == ARRAY_SIZE(profile->formats) - 1) {
/* leave at least one PCM_FORMAT_INVALID at the end */
goto end;
}
}
}
bit_mask <<= 1;
table_index++;
}
}
end:
profile->formats[num_written] = PCM_FORMAT_INVALID;
return num_written;
}
static unsigned profile_enum_channel_counts(alsa_device_profile* profile, unsigned min,
unsigned max)
{
/* modify alsa_device_profile.h if you change the std_channel_counts[] array. */
// The order of this array controls the order for channel mask generation.
// In general, this is just counting from max to min not skipping anything,
// but need not be that way.
static const unsigned std_channel_counts[FCC_24] = {
24, 23, 22, 21, 20, 19, 18, 17,
16, 15, 14, 13, 12, 11, 10, 9,
8, 7, 6, 5, 4, 3, 2, 1
};
unsigned num_counts = 0;
unsigned index;
int max_allowed_index = -1; // index of maximum allowed channel count reported by device.
/* TODO write a profile_test_channel_count() */
/* Ensure there is at least one invalid channel count to terminate the channel counts array */
for (index = 0; index < ARRAY_SIZE(std_channel_counts) &&
num_counts < ARRAY_SIZE(profile->channel_counts) - 1;
index++) {
const unsigned test_count = std_channel_counts[index];
/* TODO Do we want a channel counts test? */
if (test_count <= FCC_LIMIT) {
if (test_count >= min && test_count <= max /* &&
profile_test_channel_count(profile, channel_counts[index])*/) {
profile->channel_counts[num_counts++] = test_count;
}
if (max_allowed_index < 0 ||
std_channel_counts[max_allowed_index] < test_count) {
max_allowed_index = index;
}
}
}
// if we have no match with the standard counts, we use the largest (preferred) std count.
// Note: the usb hal will adjust channel data properly to fit.
if (num_counts == 0 && max_allowed_index >= 0) {
ALOGW("usb device does not match std channel counts, setting to %d",
std_channel_counts[max_allowed_index]);
profile->channel_counts[num_counts++] = std_channel_counts[max_allowed_index];
}
profile->channel_counts[num_counts] = 0;
return num_counts; /* return # of supported counts */
}
/*
* Reads and decodes configuration info from the specified ALSA card/device.
*/
static int read_alsa_device_config(alsa_device_profile * profile, struct pcm_config * config)
{
ALOGV("usb:audio_hw - read_alsa_device_config(c:%d d:%d t:0x%X)",
profile->card, profile->device, profile->direction);
if (profile->card < 0 || profile->device < 0) {
return -EINVAL;
}
struct pcm_params * alsa_hw_params =
pcm_params_get(profile->card, profile->device, profile->direction);
if (alsa_hw_params == NULL) {
return -EINVAL;
}
profile->min_period_size = pcm_params_get_min(alsa_hw_params, PCM_PARAM_PERIOD_SIZE);
profile->max_period_size = pcm_params_get_max(alsa_hw_params, PCM_PARAM_PERIOD_SIZE);
profile->min_channel_count = pcm_params_get_min(alsa_hw_params, PCM_PARAM_CHANNELS);
profile->max_channel_count = pcm_params_get_max(alsa_hw_params, PCM_PARAM_CHANNELS);
int ret = 0;
/*
* This Logging will be useful when testing new USB devices.
*/
#ifdef LOG_PCM_PARAMS
log_pcm_params(alsa_hw_params);
#endif
config->channels = pcm_params_get_min(alsa_hw_params, PCM_PARAM_CHANNELS);
// For output devices, let's make sure we choose at least stereo
// (assuming the device supports it).
if (profile->direction == PCM_OUT &&
config->channels < 2 && pcm_params_get_max(alsa_hw_params, PCM_PARAM_CHANNELS) >= 2) {
config->channels = 2;
}
config->rate = pcm_params_get_min(alsa_hw_params, PCM_PARAM_RATE);
// Prefer 48K or 44.1K
if (config->rate < 48000 &&
pcm_params_get_max(alsa_hw_params, PCM_PARAM_RATE) >= 48000) {
config->rate = 48000;
} else if (config->rate < 44100 &&
pcm_params_get_max(alsa_hw_params, PCM_PARAM_RATE) >= 44100) {
config->rate = 44100;
}
config->period_size = profile_calc_min_period_size(profile, config->rate);
config->period_count = pcm_params_get_min(alsa_hw_params, PCM_PARAM_PERIODS);
config->format = get_pcm_format_for_mask(pcm_params_get_mask(alsa_hw_params, PCM_PARAM_FORMAT));
#ifdef LOG_PCM_PARAMS
log_pcm_config(config, "read_alsa_device_config");
#endif
if (config->format == PCM_FORMAT_INVALID) {
ret = -EINVAL;
}
pcm_params_free(alsa_hw_params);
return ret;
}
bool profile_fill_builtin_device_info(alsa_device_profile* profile, struct pcm_config* config,
unsigned buffer_frame_count) {
if (!profile_is_initialized(profile)) {
return false;
}
profile->extra_latency_ms = property_get_int32(
"ro.hardware.audio.tinyalsa.host_latency_ms", 0);
profile->default_config.channels = config->channels;
profile->default_config.rate = config->rate;
profile->default_config.format = config->format;
int period_count = property_get_int32(
"ro.hardware.audio.tinyalsa.period_count", DEFAULT_PERIOD_COUNT);
if (period_count <= 0) period_count = DEFAULT_PERIOD_COUNT;
profile->default_config.period_count = period_count;
int period_size_multiplier = property_get_int32(
"ro.hardware.audio.tinyalsa.period_size_multiplier", 1);
if (period_size_multiplier <= 0) period_size_multiplier = 1;
profile->default_config.period_size =
period_size_multiplier * buffer_frame_count / period_count;
profile->min_period_size = profile->max_period_size = profile->default_config.period_size;
profile->formats[0] = config->format;
profile->formats[1] = PCM_FORMAT_INVALID;
profile->channel_counts[0] = config->channels;
profile->channel_counts[1] = 0;
profile->min_channel_count = profile->max_channel_count = config->channels;
profile->sample_rates[0] = config->rate;
profile->sample_rates[1] = 0;
profile->is_valid = true;
return true;
}
bool profile_read_device_info(alsa_device_profile* profile)
{
if (!profile_is_initialized(profile)) {
return false;
}
/* let's get some defaults */
read_alsa_device_config(profile, &profile->default_config);
ALOGV("default_config chans:%d rate:%d format:%d count:%d size:%d",
profile->default_config.channels, profile->default_config.rate,
profile->default_config.format, profile->default_config.period_count,
profile->default_config.period_size);
struct pcm_params * alsa_hw_params = pcm_params_get(profile->card,
profile->device,
profile->direction);
if (alsa_hw_params == NULL) {
return false;
}
/* Formats */
const struct pcm_mask * format_mask = pcm_params_get_mask(alsa_hw_params, PCM_PARAM_FORMAT);
profile_enum_sample_formats(profile, format_mask);
/* Channels */
profile_enum_channel_counts(
profile, pcm_params_get_min(alsa_hw_params, PCM_PARAM_CHANNELS),
pcm_params_get_max(alsa_hw_params, PCM_PARAM_CHANNELS));
/* Sample Rates */
profile_enum_sample_rates(
profile, pcm_params_get_min(alsa_hw_params, PCM_PARAM_RATE),
pcm_params_get_max(alsa_hw_params, PCM_PARAM_RATE));
profile->is_valid = true;
pcm_params_free(alsa_hw_params);
return true;
}
char * profile_get_sample_rate_strs(const alsa_device_profile* profile)
{
/* if we assume that rate strings are about 5 characters (48000 is 5), plus ~1 for a
* delimiter "|" this buffer has room for about 22 rate strings which seems like
* way too much, but it's a stack variable so only temporary.
*/
char buffer[128];
buffer[0] = '\0';
size_t buffSize = ARRAY_SIZE(buffer);
size_t curStrLen = 0;
char numBuffer[32];
size_t numEntries = 0;
size_t index;
for (index = 0; profile->sample_rates[index] != 0; index++) {
snprintf(numBuffer, sizeof(numBuffer), "%u", profile->sample_rates[index]);
// account for both the null, and potentially the bar.
if (buffSize - curStrLen < strlen(numBuffer) + (numEntries != 0 ? 2 : 1)) {
/* we don't have room for another, so bail at this point rather than
* return a malformed rate string
*/
break;
}
if (numEntries++ != 0) {
strlcat(buffer, "|", buffSize);
}
curStrLen = strlcat(buffer, numBuffer, buffSize);
}
return strdup(buffer);
}
char * profile_get_format_strs(const alsa_device_profile* profile)
{
/* if we assume that format strings are about 24 characters (AUDIO_FORMAT_PCM_16_BIT is 23),
* plus ~1 for a delimiter "|" this buffer has room for about 10 format strings which seems
* like way too much, but it's a stack variable so only temporary.
*/
char buffer[256];
buffer[0] = '\0';
size_t buffSize = ARRAY_SIZE(buffer);
size_t curStrLen = 0;
size_t numEntries = 0;
size_t index = 0;
for (index = 0; profile->formats[index] != PCM_FORMAT_INVALID; index++) {
// account for both the null, and potentially the bar.
if (buffSize - curStrLen < strlen(format_string_map[profile->formats[index]])
+ (numEntries != 0 ? 2 : 1)) {
/* we don't have room for another, so bail at this point rather than
* return a malformed rate string
*/
break;
}
if (numEntries++ != 0) {
strlcat(buffer, "|", buffSize);
}
curStrLen = strlcat(buffer, format_string_map[profile->formats[index]], buffSize);
}
return strdup(buffer);
}
char * profile_get_channel_count_strs(const alsa_device_profile* profile)
{
// we use only the canonical even number channel position masks.
static const char * const out_chans_strs[] = {
[0] = "AUDIO_CHANNEL_NONE", /* will never be taken as this is a terminator */
[1] = "AUDIO_CHANNEL_OUT_MONO",
[2] = "AUDIO_CHANNEL_OUT_STEREO",
[4] = "AUDIO_CHANNEL_OUT_QUAD",
[6] = "AUDIO_CHANNEL_OUT_5POINT1",
[FCC_8] = "AUDIO_CHANNEL_OUT_7POINT1",
[FCC_12] = "AUDIO_CHANNEL_OUT_7POINT1POINT4",
[FCC_24] = "AUDIO_CHANNEL_OUT_22POINT2",
};
static const char * const in_chans_strs[] = {
[0] = "AUDIO_CHANNEL_NONE", /* will never be taken as this is a terminator */
[1] = "AUDIO_CHANNEL_IN_MONO",
[2] = "AUDIO_CHANNEL_IN_STEREO",
/* channel counts greater than this not considered */
};
static const char * const index_chans_strs[] = {
[0] = "AUDIO_CHANNEL_NONE", /* will never be taken as this is a terminator */
[1] = "AUDIO_CHANNEL_INDEX_MASK_1",
[2] = "AUDIO_CHANNEL_INDEX_MASK_2",
[3] = "AUDIO_CHANNEL_INDEX_MASK_3",
[4] = "AUDIO_CHANNEL_INDEX_MASK_4",
[5] = "AUDIO_CHANNEL_INDEX_MASK_5",
[6] = "AUDIO_CHANNEL_INDEX_MASK_6",
[7] = "AUDIO_CHANNEL_INDEX_MASK_7",
[8] = "AUDIO_CHANNEL_INDEX_MASK_8",
[9] = "AUDIO_CHANNEL_INDEX_MASK_9",
[10] = "AUDIO_CHANNEL_INDEX_MASK_10",
[11] = "AUDIO_CHANNEL_INDEX_MASK_11",
[12] = "AUDIO_CHANNEL_INDEX_MASK_12",
[13] = "AUDIO_CHANNEL_INDEX_MASK_13",
[14] = "AUDIO_CHANNEL_INDEX_MASK_14",
[15] = "AUDIO_CHANNEL_INDEX_MASK_15",
[16] = "AUDIO_CHANNEL_INDEX_MASK_16",
[17] = "AUDIO_CHANNEL_INDEX_MASK_17",
[18] = "AUDIO_CHANNEL_INDEX_MASK_18",
[19] = "AUDIO_CHANNEL_INDEX_MASK_19",
[20] = "AUDIO_CHANNEL_INDEX_MASK_20",
[21] = "AUDIO_CHANNEL_INDEX_MASK_21",
[22] = "AUDIO_CHANNEL_INDEX_MASK_22",
[23] = "AUDIO_CHANNEL_INDEX_MASK_23",
[24] = "AUDIO_CHANNEL_INDEX_MASK_24",
};
const bool isOutProfile = profile->direction == PCM_OUT;
const char * const * const chans_strs = isOutProfile ? out_chans_strs : in_chans_strs;
size_t chans_strs_size =
isOutProfile ? ARRAY_SIZE(out_chans_strs) : ARRAY_SIZE(in_chans_strs);
if (chans_strs_size > FCC_LIMIT + 1) chans_strs_size = FCC_LIMIT + 1; // starts with 0.
/*
* MAX_CHANNEL_NAME_LEN: The longest channel name so far is "AUDIO_CHANNEL_OUT_7POINT1POINT4"
* at 31 chars, add 1 for the "|" delimiter, so we allocate 48 chars to be safe.
*
* We allocate room for channel index and channel position strings (2x).
*/
const size_t MAX_CHANNEL_NAME_LEN = 48;
char buffer[MAX_CHANNEL_NAME_LEN * (FCC_LIMIT * 2) + 1];
buffer[0] = '\0';
size_t buffSize = ARRAY_SIZE(buffer);
size_t curStrLen = 0;
/* We currently support MONO and STEREO, and always report STEREO but some (many)
* USB Audio Devices may only announce support for MONO (a headset mic for example), or
* The total number of output channels. SO, if the device itself doesn't explicitly
* support STEREO, append to the channel config strings we are generating.
*
* The MONO and STEREO positional channel masks are provided for legacy compatibility.
* For multichannel (n > 2) we only expose channel index masks.
*/
// Always support stereo
curStrLen = strlcat(buffer, chans_strs[2], buffSize);
size_t index;
unsigned channel_count;
for (index = 0;
(channel_count = profile->channel_counts[index]) != 0;
index++) {
if (channel_count > FCC_LIMIT) continue;
/* we only show positional information for mono (stereo handled already) */
if (channel_count < chans_strs_size
&& chans_strs[channel_count] != NULL
&& channel_count < 2 /* positional only for fewer than 2 channels */) {
// account for the '|' and the '\0'
if (buffSize - curStrLen < strlen(chans_strs[channel_count]) + 2) {
/* we don't have room for another, so bail at this point rather than
* return a malformed rate string
*/
break;
}
if (curStrLen != 0) strlcat(buffer, "|", buffSize);
curStrLen = strlcat(buffer, chans_strs[channel_count], buffSize);
}
// handle channel index masks for both input and output
// +2 to account for the '|' and the '\0'
if (buffSize - curStrLen < strlen(index_chans_strs[channel_count]) + 2) {
/* we don't have room for another, so bail at this point rather than
* return a malformed rate string
*/
break;
}
if (curStrLen != 0) strlcat(buffer, "|", buffSize);
curStrLen = strlcat(buffer, index_chans_strs[channel_count], buffSize);
}
return strdup(buffer);
}
void profile_dump(const alsa_device_profile* profile, int fd)
{
if (profile == NULL) {
dprintf(fd, " %s\n", "No USB Profile");
return; /* bail early */
}
if (!profile->is_valid) {
dprintf(fd, " Profile is INVALID");
}
/* card/device/direction */
dprintf(fd, " card:%d, device:%d - %s\n",
profile->card, profile->device, profile->direction == PCM_OUT ? "OUT" : "IN");
/* formats */
dprintf(fd, " Formats: ");
for (int fmtIndex = 0;
fmtIndex < MAX_PROFILE_FORMATS && profile->formats[fmtIndex] != PCM_FORMAT_INVALID;
fmtIndex++) {
dprintf(fd, "%d ", profile->formats[fmtIndex]);
}
dprintf(fd, "\n");
/* sample rates */
dprintf(fd, " Rates: ");
for (int rateIndex = 0;
rateIndex < MAX_PROFILE_SAMPLE_RATES && profile->sample_rates[rateIndex] != 0;
rateIndex++) {
dprintf(fd, "%u ", profile->sample_rates[rateIndex]);
}
dprintf(fd, "\n");
// channel counts
dprintf(fd, " Channel Counts: ");
for (int cntIndex = 0;
cntIndex < MAX_PROFILE_CHANNEL_COUNTS && profile->channel_counts[cntIndex] != 0;
cntIndex++) {
dprintf(fd, "%u ", profile->channel_counts[cntIndex]);
}
dprintf(fd, "\n");
dprintf(fd, " min/max period size [%u : %u]\n",
profile->min_period_size,profile-> max_period_size);
dprintf(fd, " min/max channel count [%u : %u]\n",
profile->min_channel_count, profile->max_channel_count);
// struct pcm_config default_config;
dprintf(fd, " Default Config:\n");
dprintf(fd, " channels: %d\n", profile->default_config.channels);
dprintf(fd, " rate: %d\n", profile->default_config.rate);
dprintf(fd, " period_size: %d\n", profile->default_config.period_size);
dprintf(fd, " period_count: %d\n", profile->default_config.period_count);
dprintf(fd, " format: %d\n", profile->default_config.format);
}

View File

@@ -0,0 +1,409 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "alsa_device_proxy"
/*#define LOG_NDEBUG 0*/
/*#define LOG_PCM_PARAMS 0*/
#include <log/log.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <audio_utils/clock.h>
#include "include/alsa_device_proxy.h"
#include "include/alsa_logging.h"
#define DEFAULT_PERIOD_SIZE 1024
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
// These must use the same clock. If we change ALSA clock to real time, the system
// clock must be updated, too.
#define ALSA_CLOCK_TYPE PCM_MONOTONIC
#define SYSTEM_CLOCK_TYPE CLOCK_MONOTONIC
static const unsigned format_byte_size_map[] = {
2, /* PCM_FORMAT_S16_LE */
4, /* PCM_FORMAT_S32_LE */
1, /* PCM_FORMAT_S8 */
4, /* PCM_FORMAT_S24_LE */
3, /* PCM_FORMAT_S24_3LE */
};
int proxy_prepare(alsa_device_proxy * proxy, const alsa_device_profile* profile,
struct pcm_config * config, bool require_exact_match)
{
int ret = 0;
ALOGD("proxy_prepare(c:%d, d:%d)", profile->card, profile->device);
proxy->profile = profile;
#ifdef LOG_PCM_PARAMS
log_pcm_config(config, "proxy_setup()");
#endif
if (config->format != PCM_FORMAT_INVALID && profile_is_format_valid(profile, config->format)) {
proxy->alsa_config.format = config->format;
} else if (require_exact_match) {
ret = -EINVAL;
} else {
proxy->alsa_config.format = profile->default_config.format;
ALOGW("Invalid format %d - using default %d.",
config->format, profile->default_config.format);
// Indicate override when default format was not requested
if (config->format != PCM_FORMAT_INVALID) {
ret = -EINVAL;
}
}
if (config->rate != 0 && profile_is_sample_rate_valid(profile, config->rate)) {
proxy->alsa_config.rate = config->rate;
} else if (require_exact_match) {
ret = -EINVAL;
} else {
proxy->alsa_config.rate = profile->default_config.rate;
ALOGW("Invalid sample rate %u - using default %u.",
config->rate, profile->default_config.rate);
// Indicate override when default rate was not requested
if (config->rate != 0) {
ret = -EINVAL;
}
}
if (config->channels != 0 && profile_is_channel_count_valid(profile, config->channels)) {
proxy->alsa_config.channels = config->channels;
} else if (require_exact_match) {
ret = -EINVAL;
} else {
proxy->alsa_config.channels = profile_get_closest_channel_count(profile, config->channels);
ALOGW("Invalid channel count %u - using closest %u.",
config->channels, proxy->alsa_config.channels);
// Indicate override when default channel count was not requested
if (config->channels != 0) {
ret = -EINVAL;
}
}
proxy->alsa_config.period_count = profile->default_config.period_count;
proxy->alsa_config.period_size =
profile_get_period_size(proxy->profile, proxy->alsa_config.rate);
// Hack for USB accessory audio.
// Here we set the correct value for period_count if tinyalsa fails to get it from the
// f_audio_source driver.
if (proxy->alsa_config.period_count == 0) {
proxy->alsa_config.period_count = DEFAULT_PERIOD_COUNT;
}
proxy->pcm = NULL;
// config format should be checked earlier against profile.
if (config->format >= 0 && (size_t)config->format < ARRAY_SIZE(format_byte_size_map)) {
proxy->frame_size = format_byte_size_map[config->format] * proxy->alsa_config.channels;
} else {
proxy->frame_size = 1;
}
// let's check to make sure we can ACTUALLY use the maximum rate (with the channel count)
// Note that profile->sample_rates is sorted highest to lowest, so the scan will get
// us the highest working rate
int max_rate_index = proxy_scan_rates(proxy, profile->sample_rates, require_exact_match);
if (max_rate_index >= 0) {
if (proxy->alsa_config.rate > profile->sample_rates[max_rate_index]) {
ALOGW("Limiting sampling rate from %u to %u.",
proxy->alsa_config.rate, profile->sample_rates[max_rate_index]);
proxy->alsa_config.rate = profile->sample_rates[max_rate_index];
ret = -EINVAL;
}
}
return ret;
}
int proxy_prepare_from_default_config(alsa_device_proxy * proxy,
const alsa_device_profile * profile)
{
ALOGD("proxy_prepare_from_default_config(c:%d, d:%d)", profile->card, profile->device);
proxy->profile = profile;
#ifdef LOG_PCM_PARAMS
log_pcm_config(&profile->default_config, "proxy_prepare_from_default_config()");
#endif
proxy->alsa_config.format = profile->default_config.format;
proxy->alsa_config.rate = profile->default_config.rate;
proxy->alsa_config.channels = profile->default_config.channels;
proxy->alsa_config.period_count = profile->default_config.period_count;
proxy->alsa_config.period_size = profile->default_config.period_size;
proxy->pcm = NULL;
enum pcm_format format = profile->default_config.format;
if (format >= 0 && (size_t)format < ARRAY_SIZE(format_byte_size_map)) {
proxy->frame_size = format_byte_size_map[format] * proxy->alsa_config.channels;
} else {
proxy->frame_size = 1;
}
return 0;
}
int proxy_open(alsa_device_proxy * proxy)
{
const alsa_device_profile* profile = proxy->profile;
ALOGD("proxy_open(card:%d device:%d %s)", profile->card, profile->device,
profile->direction == PCM_OUT ? "PCM_OUT" : "PCM_IN");
if (profile->card < 0 || profile->device < 0) {
return -EINVAL;
}
proxy->pcm = pcm_open(profile->card, profile->device,
profile->direction | ALSA_CLOCK_TYPE, &proxy->alsa_config);
if (proxy->pcm == NULL) {
return -ENOMEM;
}
if (!pcm_is_ready(proxy->pcm)) {
ALOGE(" proxy_open() pcm_is_ready() failed: %s", pcm_get_error(proxy->pcm));
#if defined(LOG_PCM_PARAMS)
log_pcm_config(&proxy->alsa_config, "config");
#endif
pcm_close(proxy->pcm);
proxy->pcm = NULL;
return -ENOMEM;
}
return 0;
}
void proxy_close(alsa_device_proxy * proxy)
{
ALOGD("proxy_close() [pcm:%p]", proxy->pcm);
if (proxy->pcm != NULL) {
pcm_close(proxy->pcm);
proxy->pcm = NULL;
}
}
/*
* Sample Rate
*/
unsigned proxy_get_sample_rate(const alsa_device_proxy * proxy)
{
return proxy->alsa_config.rate;
}
/*
* Format
*/
enum pcm_format proxy_get_format(const alsa_device_proxy * proxy)
{
return proxy->alsa_config.format;
}
/*
* Channel Count
*/
unsigned proxy_get_channel_count(const alsa_device_proxy * proxy)
{
return proxy->alsa_config.channels;
}
/*
* Other
*/
unsigned int proxy_get_period_size(const alsa_device_proxy * proxy)
{
return proxy->alsa_config.period_size;
}
unsigned int proxy_get_period_count(const alsa_device_proxy * proxy)
{
return proxy->alsa_config.period_count;
}
static unsigned int proxy_get_extra_latency_ms(const alsa_device_proxy * proxy)
{
return proxy->profile->extra_latency_ms;
}
unsigned proxy_get_latency(const alsa_device_proxy * proxy)
{
return (proxy_get_period_size(proxy) * proxy_get_period_count(proxy) * 1000)
/ proxy_get_sample_rate(proxy) + proxy_get_extra_latency_ms(proxy);
}
int proxy_get_presentation_position(const alsa_device_proxy * proxy,
uint64_t *frames, struct timespec *timestamp)
{
int ret = -EPERM; // -1
unsigned int avail;
struct timespec alsaTs;
if (proxy->pcm != NULL
&& pcm_get_htimestamp(proxy->pcm, &avail, &alsaTs) == 0) {
const size_t kernel_buffer_size = pcm_get_buffer_size(proxy->pcm);
if (avail > kernel_buffer_size) {
// pcm_get_htimestamp() computes the available frames by comparing the ALSA driver
// hw_ptr and the appl_ptr levels. In underrun, the hw_ptr may keep running and report
// an excessively large number available number.
ALOGW("available frames(%u) > buffer size(%zu), clamped", avail, kernel_buffer_size);
avail = kernel_buffer_size;
}
if (alsaTs.tv_sec != 0 || alsaTs.tv_nsec != 0) {
*timestamp = alsaTs;
} else { // If ALSA returned a zero timestamp, do not use it.
clock_gettime(SYSTEM_CLOCK_TYPE, timestamp);
}
int64_t signed_frames = proxy->transferred - kernel_buffer_size + avail;
// It is possible to compensate for additional driver and device delay
// by changing signed_frames. Example:
// signed_frames -= 20 /* ms */ * proxy->alsa_config.rate / 1000;
if (signed_frames >= 0) {
*frames = signed_frames;
ret = 0;
}
}
return ret;
}
int proxy_get_capture_position(const alsa_device_proxy * proxy,
int64_t *frames, int64_t *time)
{
int ret = -ENOSYS;
unsigned int avail;
struct timespec timestamp;
if (proxy->pcm != NULL
&& pcm_get_htimestamp(proxy->pcm, &avail, &timestamp) == 0) {
if (timestamp.tv_sec == 0 && timestamp.tv_nsec == 0) {
// If ALSA returned a zero timestamp, do not use it.
clock_gettime(SYSTEM_CLOCK_TYPE, &timestamp);
}
uint64_t framesTemp = proxy->transferred + avail;
if (framesTemp > INT64_MAX) {
framesTemp -= INT64_MAX;
}
*frames = framesTemp;
*time = audio_utils_ns_from_timespec(&timestamp);
ret = 0;
}
return ret;
}
int proxy_stop(alsa_device_proxy * proxy)
{
int ret = -ENOSYS;
if (proxy->pcm != NULL) ret = pcm_stop(proxy->pcm);
return ret;
}
/*
* I/O
*/
int proxy_write(alsa_device_proxy * proxy, const void *data, unsigned int count)
{
return proxy_write_with_retries(proxy, data, count, 1);
}
int proxy_write_with_retries(
alsa_device_proxy * proxy, const void *data, unsigned int count, int tries)
{
while (true) {
--tries;
const int ret = pcm_write(proxy->pcm, data, count);
if (ret == 0) {
proxy->transferred += count / proxy->frame_size;
return 0;
} else if (tries > 0 && (ret == -EIO || ret == -EAGAIN)) {
continue;
}
return ret;
}
}
int proxy_read(alsa_device_proxy * proxy, void *data, unsigned int count)
{
return proxy_read_with_retries(proxy, data, count, 1);
}
int proxy_read_with_retries(alsa_device_proxy * proxy, void *data, unsigned int count, int tries)
{
while (true) {
--tries;
const int ret = pcm_read(proxy->pcm, data, count);
if (ret == 0) {
proxy->transferred += count / proxy->frame_size;
return 0;
} else if (tries > 0 && (ret == -EIO || ret == -EAGAIN)) {
continue;
}
return ret;
}
}
/*
* Debugging
*/
void proxy_dump(const alsa_device_proxy* proxy, int fd)
{
if (proxy != NULL) {
dprintf(fd, " channels: %d\n", proxy->alsa_config.channels);
dprintf(fd, " rate: %d\n", proxy->alsa_config.rate);
dprintf(fd, " period_size: %d\n", proxy->alsa_config.period_size);
dprintf(fd, " period_count: %d\n", proxy->alsa_config.period_count);
dprintf(fd, " format: %d\n", proxy->alsa_config.format);
}
}
int proxy_scan_rates(alsa_device_proxy * proxy,
const unsigned sample_rates[],
bool require_exact_match) {
const alsa_device_profile* profile = proxy->profile;
if (profile->card < 0 || profile->device < 0) {
return -EINVAL;
}
struct pcm_config alsa_config;
memcpy(&alsa_config, &proxy->alsa_config, sizeof(alsa_config));
struct pcm * alsa_pcm;
int rate_index = 0;
while (sample_rates[rate_index] != 0) {
if (require_exact_match && alsa_config.rate != sample_rates[rate_index]) {
rate_index++;
continue;
}
alsa_config.rate = sample_rates[rate_index];
alsa_pcm = pcm_open(profile->card, profile->device,
profile->direction | ALSA_CLOCK_TYPE, &alsa_config);
if (alsa_pcm != NULL) {
if (pcm_is_ready(alsa_pcm)) {
pcm_close(alsa_pcm);
return rate_index;
}
pcm_close(alsa_pcm);
}
rate_index++;
}
return -EINVAL;
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "alsa_format"
/*#define LOG_NDEBUG 0*/
#include "include/alsa_format.h"
#include <tinyalsa/asoundlib.h>
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
/*
* Maps from bit position in pcm_mask to PCM_ format constants.
*/
int8_t const pcm_format_value_map[50] = {
PCM_FORMAT_S8, /* 00 - SNDRV_PCM_FORMAT_S8 */
PCM_FORMAT_INVALID, /* 01 - SNDRV_PCM_FORMAT_U8 */
PCM_FORMAT_S16_LE, /* 02 - SNDRV_PCM_FORMAT_S16_LE */
PCM_FORMAT_INVALID, /* 03 - SNDRV_PCM_FORMAT_S16_BE */
PCM_FORMAT_INVALID, /* 04 - SNDRV_PCM_FORMAT_U16_LE */
PCM_FORMAT_INVALID, /* 05 - SNDRV_PCM_FORMAT_U16_BE */
PCM_FORMAT_S24_LE, /* 06 - SNDRV_PCM_FORMAT_S24_LE */
PCM_FORMAT_INVALID, /* 07 - SNDRV_PCM_FORMAT_S24_BE */
PCM_FORMAT_INVALID, /* 08 - SNDRV_PCM_FORMAT_U24_LE */
PCM_FORMAT_INVALID, /* 09 - SNDRV_PCM_FORMAT_U24_BE */
PCM_FORMAT_S32_LE, /* 10 - SNDRV_PCM_FORMAT_S32_LE */
PCM_FORMAT_INVALID, /* 11 - SNDRV_PCM_FORMAT_S32_BE */
PCM_FORMAT_INVALID, /* 12 - SNDRV_PCM_FORMAT_U32_LE */
PCM_FORMAT_INVALID, /* 13 - SNDRV_PCM_FORMAT_U32_BE */
PCM_FORMAT_INVALID, /* 14 - SNDRV_PCM_FORMAT_FLOAT_LE */
PCM_FORMAT_INVALID, /* 15 - SNDRV_PCM_FORMAT_FLOAT_BE */
PCM_FORMAT_INVALID, /* 16 - SNDRV_PCM_FORMAT_FLOAT64_LE */
PCM_FORMAT_INVALID, /* 17 - SNDRV_PCM_FORMAT_FLOAT64_BE */
PCM_FORMAT_INVALID, /* 18 - SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE */
PCM_FORMAT_INVALID, /* 19 - SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE */
PCM_FORMAT_INVALID, /* 20 - SNDRV_PCM_FORMAT_MU_LAW */
PCM_FORMAT_INVALID, /* 21 - SNDRV_PCM_FORMAT_A_LAW */
PCM_FORMAT_INVALID, /* 22 - SNDRV_PCM_FORMAT_IMA_ADPCM */
PCM_FORMAT_INVALID, /* 23 - SNDRV_PCM_FORMAT_MPEG */
PCM_FORMAT_INVALID, /* 24 - SNDRV_PCM_FORMAT_GSM */
PCM_FORMAT_INVALID, /* 25 -> 30 (not assigned) */
PCM_FORMAT_INVALID,
PCM_FORMAT_INVALID,
PCM_FORMAT_INVALID,
PCM_FORMAT_INVALID,
PCM_FORMAT_INVALID,
PCM_FORMAT_INVALID, /* 31 - SNDRV_PCM_FORMAT_SPECIAL */
PCM_FORMAT_S24_3LE, /* 32 - SNDRV_PCM_FORMAT_S24_3LE */
PCM_FORMAT_INVALID, /* 33 - SNDRV_PCM_FORMAT_S24_3BE */
PCM_FORMAT_INVALID, /* 34 - SNDRV_PCM_FORMAT_U24_3LE */
PCM_FORMAT_INVALID, /* 35 - SNDRV_PCM_FORMAT_U24_3BE */
PCM_FORMAT_INVALID, /* 36 - SNDRV_PCM_FORMAT_S20_3LE */
PCM_FORMAT_INVALID, /* 37 - SNDRV_PCM_FORMAT_S20_3BE */
PCM_FORMAT_INVALID, /* 38 - SNDRV_PCM_FORMAT_U20_3LE */
PCM_FORMAT_INVALID, /* 39 - SNDRV_PCM_FORMAT_U20_3BE */
PCM_FORMAT_INVALID, /* 40 - SNDRV_PCM_FORMAT_S18_3LE */
PCM_FORMAT_INVALID, /* 41 - SNDRV_PCM_FORMAT_S18_3BE */
PCM_FORMAT_INVALID, /* 42 - SNDRV_PCM_FORMAT_U18_3LE */
PCM_FORMAT_INVALID, /* 43 - SNDRV_PCM_FORMAT_U18_3BE */
PCM_FORMAT_INVALID, /* 44 - SNDRV_PCM_FORMAT_G723_24 */
PCM_FORMAT_INVALID, /* 45 - SNDRV_PCM_FORMAT_G723_24_1B */
PCM_FORMAT_INVALID, /* 46 - SNDRV_PCM_FORMAT_G723_40 */
PCM_FORMAT_INVALID, /* 47 - SNDRV_PCM_FORMAT_G723_40_1B */
PCM_FORMAT_INVALID, /* 48 - SNDRV_PCM_FORMAT_DSD_U8 */
PCM_FORMAT_INVALID /* 49 - SNDRV_PCM_FORMAT_DSD_U16_LE */
};
/*
* Scans the provided format mask and returns the first non-8 bit sample
* format supported by the devices.
*/
enum pcm_format get_pcm_format_for_mask(const struct pcm_mask* mask)
{
int num_slots = ARRAY_SIZE(mask->bits);
int bits_per_slot = sizeof(mask->bits[0]) * 8;
int table_size = ARRAY_SIZE(pcm_format_value_map);
int slot_index, bit_index, table_index;
table_index = 0;
for (slot_index = 0; slot_index < num_slots && table_index < table_size; slot_index++) {
unsigned bit_mask = 1;
for (bit_index = 0; bit_index < bits_per_slot && table_index < table_size; bit_index++) {
/* skip any 8-bit formats */
if (table_index >= 2 && (mask->bits[slot_index] & bit_mask) != 0) {
/* just return the first one which will be at least 16-bit */
return (int)pcm_format_value_map[table_index];
}
bit_mask <<= 1;
table_index++;
}
}
return PCM_FORMAT_INVALID;
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "alsa_logging"
/*#define LOG_NDEBUG 0*/
#include <string.h>
#include <log/log.h>
#include "include/alsa_logging.h"
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
/*
* Logging
*/
void log_pcm_mask(const char* mask_name, const struct pcm_mask* mask)
{
const size_t num_slots = ARRAY_SIZE(mask->bits);
const size_t bits_per_slot = (sizeof(mask->bits[0]) * 8);
const size_t chars_per_slot = (bits_per_slot + 1); /* comma */
const size_t BUFF_SIZE =
(num_slots * chars_per_slot + 2 + 1); /* brackets and null-terminator */
char buff[BUFF_SIZE];
buff[0] = '\0';
size_t slot_index, bit_index;
strcat(buff, "[");
for (slot_index = 0; slot_index < num_slots; slot_index++) {
unsigned bit_mask = 1;
for (bit_index = 0; bit_index < bits_per_slot; bit_index++) {
strcat(buff, (mask->bits[slot_index] & bit_mask) != 0 ? "1" : "0");
bit_mask <<= 1;
}
if (slot_index < num_slots - 1) {
strcat(buff, ",");
}
}
strcat(buff, "]");
ALOGV("%s: mask:%s", mask_name, buff);
}
void log_pcm_params(const struct pcm_params * alsa_hw_params)
{
ALOGV("usb:audio_hw - PCM_PARAM_SAMPLE_BITS min:%u, max:%u",
pcm_params_get_min(alsa_hw_params, PCM_PARAM_SAMPLE_BITS),
pcm_params_get_max(alsa_hw_params, PCM_PARAM_SAMPLE_BITS));
ALOGV("usb:audio_hw - PCM_PARAM_FRAME_BITS min:%u, max:%u",
pcm_params_get_min(alsa_hw_params, PCM_PARAM_FRAME_BITS),
pcm_params_get_max(alsa_hw_params, PCM_PARAM_FRAME_BITS));
log_pcm_mask("PCM_PARAM_FORMAT",
pcm_params_get_mask(alsa_hw_params, PCM_PARAM_FORMAT));
log_pcm_mask("PCM_PARAM_SUBFORMAT",
pcm_params_get_mask(alsa_hw_params, PCM_PARAM_SUBFORMAT));
ALOGV("usb:audio_hw - PCM_PARAM_CHANNELS min:%u, max:%u",
pcm_params_get_min(alsa_hw_params, PCM_PARAM_CHANNELS),
pcm_params_get_max(alsa_hw_params, PCM_PARAM_CHANNELS));
ALOGV("usb:audio_hw - PCM_PARAM_RATE min:%u, max:%u",
pcm_params_get_min(alsa_hw_params, PCM_PARAM_RATE),
pcm_params_get_max(alsa_hw_params, PCM_PARAM_RATE));
ALOGV("usb:audio_hw - PCM_PARAM_PERIOD_TIME min:%u, max:%u",
pcm_params_get_min(alsa_hw_params, PCM_PARAM_PERIOD_TIME),
pcm_params_get_max(alsa_hw_params, PCM_PARAM_PERIOD_TIME));
ALOGV("usb:audio_hw - PCM_PARAM_PERIOD_SIZE min:%u, max:%u",
pcm_params_get_min(alsa_hw_params, PCM_PARAM_PERIOD_SIZE),
pcm_params_get_max(alsa_hw_params, PCM_PARAM_PERIOD_SIZE));
ALOGV("usb:audio_hw - PCM_PARAM_PERIOD_BYTES min:%u, max:%u",
pcm_params_get_min(alsa_hw_params, PCM_PARAM_PERIOD_BYTES),
pcm_params_get_max(alsa_hw_params, PCM_PARAM_PERIOD_BYTES));
ALOGV("usb:audio_hw - PCM_PARAM_PERIODS min:%u, max:%u",
pcm_params_get_min(alsa_hw_params, PCM_PARAM_PERIODS),
pcm_params_get_max(alsa_hw_params, PCM_PARAM_PERIODS));
ALOGV("usb:audio_hw - PCM_PARAM_BUFFER_TIME min:%u, max:%u",
pcm_params_get_min(alsa_hw_params, PCM_PARAM_BUFFER_TIME),
pcm_params_get_max(alsa_hw_params, PCM_PARAM_BUFFER_TIME));
ALOGV("usb:audio_hw - PCM_PARAM_BUFFER_SIZE min:%u, max:%u",
pcm_params_get_min(alsa_hw_params, PCM_PARAM_BUFFER_SIZE),
pcm_params_get_max(alsa_hw_params, PCM_PARAM_BUFFER_SIZE));
ALOGV("usb:audio_hw - PCM_PARAM_BUFFER_BYTES min:%u, max:%u",
pcm_params_get_min(alsa_hw_params, PCM_PARAM_BUFFER_BYTES),
pcm_params_get_max(alsa_hw_params, PCM_PARAM_BUFFER_BYTES));
ALOGV("usb:audio_hw - PCM_PARAM_TICK_TIME min:%u, max:%u",
pcm_params_get_min(alsa_hw_params, PCM_PARAM_TICK_TIME),
pcm_params_get_max(alsa_hw_params, PCM_PARAM_TICK_TIME));
}
void log_pcm_config(const struct pcm_config * config, const char* label) {
ALOGV("log_pcm_config() - %s", label);
ALOGV(" channels:%d", config->channels);
ALOGV(" rate:%d", config->rate);
ALOGV(" period_size:%d", config->period_size);
ALOGV(" period_count:%d", config->period_count);
ALOGV(" format:%d", config->format);
#if 0
/* Values to use for the ALSA start, stop and silence thresholds. Setting
* any one of these values to 0 will cause the default tinyalsa values to be
* used instead. Tinyalsa defaults are as follows.
*
* start_threshold : period_count * period_size
* stop_threshold : period_count * period_size
* silence_threshold : 0
*/
unsigned int start_threshold;
unsigned int stop_threshold;
unsigned int silence_threshold;
/* Minimum number of frames available before pcm_mmap_write() will actually
* write into the kernel buffer. Only used if the stream is opened in mmap mode
* (pcm_open() called with PCM_MMAP flag set). Use 0 for default.
*/
int avail_min;
#endif
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_SYSTEM_MEDIA_ALSA_UTILS_ALSA_DEVICE_PROFILE_H
#define ANDROID_SYSTEM_MEDIA_ALSA_UTILS_ALSA_DEVICE_PROFILE_H
#include <stdbool.h>
#include <system/audio.h>
#include <tinyalsa/asoundlib.h>
#define MAX_PROFILE_FORMATS 6 /* We long support the 5 standard formats defined
* in asound.h, so we just need this to be 1 more
* than that */
#define MAX_PROFILE_SAMPLE_RATES 14 /* this number needs to be 1 more than the number of
* sample rates in std_sample_rates[]
* (in alsa_device_profile.c) */
#define MAX_PROFILE_CHANNEL_COUNTS (FCC_LIMIT + 1)
/* this number need to be 1 more than the number of
* standard channel formats in std_channel_counts[]
* (in alsa_device_profile.c) */
#define DEFAULT_SAMPLE_RATE 44100
#define DEFAULT_SAMPLE_FORMAT PCM_FORMAT_S16_LE
#define DEFAULT_CHANNEL_COUNT 2
#define DEFAULT_PERIOD_COUNT 4
typedef struct {
int card;
int device;
int direction; /* PCM_OUT or PCM_IN */
int extra_latency_ms; /* any extra latency in addition to the buffer */
enum pcm_format formats[MAX_PROFILE_FORMATS];
/* note that this list is sorted highest rate to lowest */
unsigned sample_rates[MAX_PROFILE_SAMPLE_RATES];
unsigned channel_counts[MAX_PROFILE_CHANNEL_COUNTS];
bool is_valid;
/* read from the hardware device */
struct pcm_config default_config;
unsigned min_period_size;
unsigned max_period_size;
unsigned min_channel_count;
unsigned max_channel_count;
} alsa_device_profile;
void profile_init(alsa_device_profile* profile, int direction);
bool profile_is_initialized(const alsa_device_profile* profile);
bool profile_is_valid(const alsa_device_profile* profile);
bool profile_is_cached_for(const alsa_device_profile* profile, int card, int device);
void profile_decache(alsa_device_profile* profile);
bool profile_fill_builtin_device_info(alsa_device_profile* profile, struct pcm_config* config,
unsigned buffer_frame_count);
bool profile_read_device_info(alsa_device_profile* profile);
/* Audio Config Strings Methods */
char * profile_get_sample_rate_strs(const alsa_device_profile* profile);
char * profile_get_format_strs(const alsa_device_profile* profile);
char * profile_get_channel_count_strs(const alsa_device_profile* profile);
/* Sample Rate Methods */
unsigned profile_get_default_sample_rate(const alsa_device_profile* profile);
unsigned profile_get_highest_sample_rate(const alsa_device_profile* profile);
bool profile_is_sample_rate_valid(const alsa_device_profile* profile, unsigned rate);
/* Format Methods */
enum pcm_format profile_get_default_format(const alsa_device_profile* profile);
bool profile_is_format_valid(const alsa_device_profile* profile, enum pcm_format fmt);
/* Channel Methods */
unsigned profile_get_default_channel_count(const alsa_device_profile* profile);
unsigned profile_get_closest_channel_count(const alsa_device_profile* profile, unsigned count);
bool profile_is_channel_count_valid(const alsa_device_profile* profile, unsigned count);
/* Utility */
unsigned profile_calc_min_period_size(const alsa_device_profile* profile, unsigned sample_rate);
unsigned int profile_get_period_size(const alsa_device_profile* profile, unsigned sample_rate);
/* Debugging */
void profile_dump(const alsa_device_profile* profile, int fd);
#endif /* ANDROID_SYSTEM_MEDIA_ALSA_UTILS_ALSA_DEVICE_PROFILE_H */

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_SYSTEM_MEDIA_ALSA_UTILS_ALSA_DEVICE_PROXY_H
#define ANDROID_SYSTEM_MEDIA_ALSA_UTILS_ALSA_DEVICE_PROXY_H
#include <tinyalsa/asoundlib.h>
#include "alsa_device_profile.h"
typedef struct {
const alsa_device_profile* profile;
struct pcm_config alsa_config;
struct pcm * pcm;
size_t frame_size; /* valid after proxy_prepare(), the frame size in bytes */
uint64_t transferred; /* the total frames transferred, not cleared on standby */
} alsa_device_proxy;
/* State */
int proxy_prepare(alsa_device_proxy * proxy, const alsa_device_profile * profile,
struct pcm_config * config, bool require_exact_match);
int proxy_prepare_from_default_config(
alsa_device_proxy * proxy, const alsa_device_profile * profile);
int proxy_open(alsa_device_proxy * proxy);
void proxy_close(alsa_device_proxy * proxy);
int proxy_get_presentation_position(const alsa_device_proxy * proxy,
uint64_t *frames, struct timespec *timestamp);
int proxy_get_capture_position(const alsa_device_proxy * proxy,
int64_t *frames, int64_t *time);
int proxy_stop(alsa_device_proxy * proxy);
/* Attributes */
unsigned proxy_get_sample_rate(const alsa_device_proxy * proxy);
enum pcm_format proxy_get_format(const alsa_device_proxy * proxy);
unsigned proxy_get_channel_count(const alsa_device_proxy * proxy);
unsigned int proxy_get_period_size(const alsa_device_proxy * proxy);
unsigned proxy_get_latency(const alsa_device_proxy * proxy);
/*
* Scans the provided list of sample rates and finds the first one that works.
*
* returns the index of the first rate for which the ALSA device can be opened.
* return negative value if none work or an error occurs.
*/
int proxy_scan_rates(alsa_device_proxy * proxy, const unsigned sample_rates[],
bool require_exact_match);
/* I/O */
int proxy_write(alsa_device_proxy * proxy, const void *data, unsigned int count);
int proxy_write_with_retries(
alsa_device_proxy * proxy, const void *data, unsigned int count, int tries);
int proxy_read(alsa_device_proxy * proxy, void *data, unsigned int count);
int proxy_read_with_retries(
alsa_device_proxy * proxy, void *data, unsigned int count, int tries);
/* Debugging */
void proxy_dump(const alsa_device_proxy * proxy, int fd);
#endif /* ANDROID_SYSTEM_MEDIA_ALSA_UTILS_ALSA_DEVICE_PROXY_H */

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_SYSTEM_MEDIA_ALSA_UTILS_ALSA_FORMAT_H
#define ANDROID_SYSTEM_MEDIA_ALSA_UTILS_ALSA_FORMAT_H
#include <system/audio.h>
#include <tinyalsa/asoundlib.h>
enum pcm_format get_pcm_format_for_mask(const struct pcm_mask* mask);
#endif /* ANDROID_SYSTEM_MEDIA_ALSA_UTILS_ALSA_FORMAT_H */

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_SYSTEM_MEDIA_ALSA_UTILS_ALSA_LOGGING_H
#define ANDROID_SYSTEM_MEDIA_ALSA_UTILS_ALSA_LOGGING_H
#include <tinyalsa/asoundlib.h>
void log_pcm_mask(const char* mask_name, const struct pcm_mask* mask);
void log_pcm_params(const struct pcm_params * alsa_hw_params);
void log_pcm_config(const struct pcm_config * config, const char* label);
#endif /* ANDROID_SYSTEM_MEDIA_ALSA_UTILS_ALSA_LOGGING_H */