diff --git a/alsa-capabilities b/alsa-capabilities index d265a3c..3b044dc 100755 --- a/alsa-capabilities +++ b/alsa-capabilities @@ -34,7 +34,7 @@ LANG=C APP_NAME_AC="alsa-capabilities" -APP_VERSION="0.5" +APP_VERSION="0.6" ## set DEBUG to a non empty value to display internal program flow to ## stderr @@ -384,51 +384,113 @@ function fetch_alsa_outputinterfaces() { ## construct the path to the hwparams file alsa_if_hwparamsfile="/proc/asound/card${alsa_dev_nr}/pcm${alsa_if_nr}p/sub0/hw_params" alsa_if_statusfile="/proc/asound/card${alsa_dev_nr}/pcm${alsa_if_nr}p/sub0/status" - ## check if the chardev exists - if [[ ! -c "${alsa_if_chardev}" ]]; then - [[ ! -z ${DEBUG} ]] && \ - debug "alsa_if_chardev \`${alsa_if_chardev}' is not a chardev." - alsa_if_chardev="${alsa_if_chardev} (${MSG_ERROR_CHARDEV_NOT_ACCESSIBLE})" + ## before determining whether this is a usb device, assume + ## the monitor file is the hwparams file + alsa_if_monitorfile="${alsa_if_hwparamsfile}" + ## assume stream file for the interface (ie + ## `/proc/asound/cardX/streamY') to determine whether + ## the interface is a uac device, and if so, which class it is + alsa_if_streamfile="/proc/asound/card${alsa_dev_nr}/stream${alsa_if_nr}" + ## keep track of status of interface + alsa_if_closed=0 + ## assume no uac device + alsa_if_uacclass="${MSG_PROP_NOTAVAILABLE}" + + if [[ ! -z ${TESTFILE} ]]; then + ## device is not real alsa_if_formats+=("(${MSG_ERROR_CHARDEV_NOFORMATS})") - alsa_if_monitorfile="${alsa_if_hwparamsfile} (error: not accessible)" - else - [[ ! -z ${DEBUG} ]] && \ - debug "alsa_if_chardev \`${alsa_if_chardev}' is a valid chardev." - if [[ "$(< ${alsa_if_statusfile})" != "closed" ]]; then - ## device is being used, get the locking pid - res="$(get_locking_process "${alsa_if_chardev}")" - if [[ $? -ne 0 ]]; then - ## can't determine - [[ ! -z ${DEBUG} ]] && \ - debug "${FUNCNAME}: get_locking_process returned error: \`${res}'" - - alsa_if_chardev="${alsa_if_chardev} (${MSG_ERROR_LOCKED_UNKNOWN}" - alsa_if_formats+=("(${MSG_ERROR_DEVICE_LOCKED})") - else - if [[ ! -z "${res}" ]]; then - alsa_if_chardev="${alsa_if_chardev} ${res}" + alsa_if_uacclass_nr="?" + alsa_if_uacclass_label="${MSG_PROP_NOTAVAILABLE}" + else + ## check if the hwparams file exists + if [[ ! -f "${alsa_if_hwparamsfile}" ]]; then + alsa_if_hwparamsfile="${alsa_if_hwparamsfile} (error: not accessible)" + fi + ## check if the chardev exists + if [[ ! -c "${alsa_if_chardev}" ]]; then + [[ ! -z ${DEBUG} ]] && \ + debug "alsa_if_chardev \`${alsa_if_chardev}' is not a chardev." + alsa_if_chardev="${alsa_if_chardev} (${MSG_ERROR_NOT_CHARDEV})" + else + [[ ! -z ${DEBUG} ]] && \ + debug "alsa_if_chardev \`${alsa_if_chardev}' is a valid chardev." + if [[ "$(< ${alsa_if_statusfile})" != "closed" ]]; then + ## device is being used, get the locking pid + alsa_if_closed=1 + res="$(get_locking_process "${alsa_if_chardev}")" + if [[ $? -ne 0 ]]; then + ## can't determine + [[ ! -z ${DEBUG} ]] && \ + debug "${FUNCNAME}: get_locking_process returned error: \`${res}'" + + alsa_if_chardev="${alsa_if_chardev} (${MSG_ERROR_LOCKED_UNKNOWN}" + alsa_if_formats+=("(${MSG_ERROR_DEVICE_LOCKED})") + else + if [[ ! -z "${res}" ]]; then + alsa_if_chardev="${alsa_if_chardev} ${res}" + fi + ## can determine; get the formats the interface + ## natively handles or return the process id and + ## name blocking the interface + alsa_if_formats+=("(${MSG_ERROR_DETERMINE_FORMATS})") fi - ## can determine; get the formats the interface - ## natively handles or return the process id and - ## name blocking the interface - alsa_if_formats+=("(${MSG_ERROR_DETERMINE_FORMATS})") fi + fi + ## device is real and not being used + ## check whether the monitor file exists; it always should + if [[ ! -f ${alsa_if_monitorfile} ]]; then + msg="${alsa_if_monitorfile} ${MSG_ERROR_NOFILE} (${MSG_ERROR_UNEXPECTED})" + alsa_if_monitorfile="${msg}" + [[ ! -z ${DEBUG} ]] && \ + debug "${MSG_ERROR_UNEXPECTED}: alsa_if_monitorfile \ +\`${alsa_if_monitorfile}' ${MSG_ERROR_NOFILE}" + fi + ## check whether the streamfile exists; it only should + ## exist in the case of a uac interface + if [[ ! -f "${alsa_if_streamfile}" ]]; then + [[ ! -z ${DEBUG} ]] && \ + debug "alsa_if_streamfile \`${alsa_if_streamfile}' \ +${MSG_ERROR_NOFILE}" + ## no uac interface + alsa_if_streamfile="" else - alsa_if_formats+=("$(return_alsa_formats "${alsa_if_hwaddress}")") + [[ ! -z ${DEBUG} ]] && \ + debug "using alsa_if_streamfile \`${alsa_if_streamfile}'." + ## set interface to usb out + alsa_if_type="uo" + ## uac devices will use the stream file instead of + ## hwparams file to monitor + alsa_if_monitorfile="${alsa_if_streamfile}" + ## get the type of uac endpoint + alsa_if_uac_ep="$(return_alsa_uac_ep "${alsa_if_streamfile}")" + if [[ $? -ne 0 ]]; then + alsa_if_uacclass_nr="?" + alsa_if_uacclass_label="${MSG_PROP_NOTAVAILABLE}" + else + [[ ! -z ${DEBUG} ]] && \ + debug "alsa_if_uac_ep set to \`${alsa_if_uac_ep}'." + ## lookup the uac class in the array for this type of endpoint (EP) + ## (for readability) + alsa_if_uacclass="${UO_EP_LABELS[${alsa_if_uac_ep}]}" + ## the uac class number (0, 1, 2 or 3) according to ./sound/usb/card.h + alsa_if_uacclass_nr="${alsa_if_uacclass% - *}" + classnr_regexp='^[0-3]+$' + if [[ ! ${alsa_if_uacclass_nr} =~ ${classnr_regexp} ]]; then + [[ ! -z ${DEBUG} ]] && \ + debug "invalid uac class number \`${alsa_if_uacclass_nr}'. \ +${MSG_ERROR_UNEXPECTED}" + alsa_if_uacclass_nr="?" + fi + ## the uac label (ie everything after `x: ') + alsa_if_uacclass_label="${alsa_if_uacclass:4}" + fi + fi fi + fi - ## check if the hwparams file exists - if [[ ! -f "${alsa_if_hwparamsfile}" ]]; then - alsa_if_hwparamsfile="${alsa_if_hwparamsfile} (error: not accessible)" - fi - - ## before determining whether this is a usb device, assume - ## the monitor file is the hwparams file - alsa_if_monitorfile="${alsa_if_hwparamsfile}" - - ## check if the interface name matches one of the strings - ## in the digital filter array + ## for non-uac interfaces: check whether it is some other digital interface + if [[ ! "${alsa_if_type}" = "uo" ]]; then for filter in "${DO_INTERFACE_FILTER[@]}"; do ## `,,' downcases the string, while `*var*' does a wildcard match if [[ "${alsa_if_name,,}" == *"${filter}"* ]]; then @@ -440,55 +502,11 @@ function fetch_alsa_outputinterfaces() { break fi done - - ## try to get the stream file for the interface (ie - ## `/proc/asound/cardX/streamY'); such determines whether - ## its a uac device, and if so, which class it is - alsa_streamfile="/proc/asound/card${alsa_dev_nr}/stream${alsa_if_nr}" - msg_alsa_streamfile="alsa_streamfile \`${alsa_streamfile}'" - if [[ ! -f "${alsa_streamfile}" ]] ;then - [[ ! -z ${DEBUG} ]] && \ - debug "${msg_alsa_streamfile} ${MSG_ERROR_NOFILE}" - ## no uac interface - alsa_if_uacclass="${MSG_PROP_NOTAVAILABLE}" - else - [[ ! -z ${DEBUG} ]] && \ - debug "${msg_alsa_streamfile} is an accessible file." - ## set interface to usb out - alsa_if_type="uo" - ## uac devices will use the stream file instead of - ## hwparams file to monitor - alsa_if_monitorfile="${alsa_streamfile}" - ## get the type of uac endpoint - alsa_if_uac_ep="$(return_alsa_uac_ep "${alsa_streamfile}")" - if [[ $? -ne 0 ]]; then - alsa_if_uacclass="${MSG_PROP_NOTAVAILABLE}" - alsa_if_uacclass_nr="?" - alsa_if_uacclass_label="${MSG_PROP_NOTAVAILABLE}" - else - [[ ! -z ${DEBUG} ]] && \ - debug "alsa_if_uac_ep set to \`${alsa_if_uac_ep}'." - ## lookup the uac class in the array for this type of endpoint (EP) - ## (for readability) - alsa_if_uacclass="${UO_EP_LABELS[${alsa_if_uac_ep}]}" - ## the uac class number (0, 1, 2 or 3) according to ./sound/usb/card.h - alsa_if_uacclass_nr="${alsa_if_uacclass% - *}" - classnr_regexp='^[0-3]+$' - if [[ ! ${alsa_if_uacclass_nr} =~ ${classnr_regexp} ]]; then - [[ ! -z ${DEBUG} ]] && \ - debug "invalid uac class number \`${alsa_if_uacclass_nr}'. \ -${MSG_ERROR_UNEXPECTED}" - alsa_if_uacclass_nr="?" - fi - ## the uac label (ie everything after `x: ') - alsa_if_uacclass_label="${alsa_if_uacclass:4}" - fi - fi fi - - ## for each type of interface, store a `hardware address' and - ## `monitoring file' pair in the proper array and construct - ## the display title + ## see if the interface type matches the user specified + ## filters and if so construct titles and store a pair of + ## hardware address and monitoring file in the proper array + match= case "${alsa_if_type}" in "ao") ## only if neither `OPT_LIMIT_DO' and `OPT_LIMIT_UO' are set @@ -505,9 +523,16 @@ ${MSG_ERROR_UNEXPECTED}" [[ ! -z ${OPT_LIMIT_AO} ]] && \ continue || match="true" esac + if [[ ! -z ${match} ]]; then + ## put each encoding format and possibily the sample rates + ## in an array + alsa_if_formats+=("$(return_alsa_formats \ +"${alsa_if_hwaddress}" \ +"${alsa_if_type}" \ +"${alsa_if_streamfile}")") - if [[ ! -z "${match}" ]]; then alsa_if_title_label="${ALSA_IF_LABELS[${alsa_if_type}]}" + ## reconstruct the label if it contained square brackets if [[ "${alsa_dev_label}" =~ .*${seperator_start}.* ]]; then alsa_dev_label="${alsa_dev_label//\*##/\[}" alsa_dev_label="${alsa_dev_label//##\*/\]}" @@ -529,6 +554,7 @@ ${MSG_ERROR_UNEXPECTED}" fi if [[ -z "${OPT_QUIET}" ]]; then + ## print the list to std_err printf "%s\n" "${alsa_if_display_title}" 1>&2; printf " - %-17s = %-60s\n" \ "${MSG_ALSA_DEVNAME}" "${alsa_dev_label}" 1>&2; @@ -560,12 +586,10 @@ ${MSG_ERROR_UNEXPECTED}" "${MSG_ALSA_ENCODINGFORMATS}" \ "${formats}" 1>&2; fi - printf " - %-17s = %-60s\n" \ "${MSG_ALSA_MONITORFILE}" "${alsa_if_monitorfile}" 1>&2; printf "\n" fi - done } @@ -576,7 +600,6 @@ function get_locking_process() { [[ ! -z ${DEBUG} ]] && \ debug_function_ac "${FUNCNAME}" "$*" - chardev_path="$1" ## try fuser which returns the pid in std_out. res="$(${CMD_FUSER} -v ${chardev_path} 2>&1)" @@ -672,52 +695,98 @@ LANG=C ${CMD_APLAY} "${aplay_opts_pre[@]}" "${aplay_opts_post[@]}" 2>&1 >/dev/nu } function return_alsa_formats() { - ## fetch and return a comma separated string of playback - ## formats by feeding aplay dummy input (--format=MPEG) while - ## making sure the test is silent by redirecting output to - ## /dev/null. - ## - ## needs address of alsa output device in `hw:x,y' format - ## as single argument ($1) + ## fetch and return a comma separated string of playback formats + ## for the interface specified in $1, of type $2. For non-uac + ## interfaces: feed dummy input to aplay (--format=MPEG). For uac + ## types: filter it directly from its stream file $3. [[ ! -z ${DEBUG} ]] && \ debug_function_ac "${FUNCNAME}" "$*" alsa_hw_device="$1" + alsa_if_type="$2" + alsa_if_streamfile="$3" aplay_opts=(-D ${alsa_hw_device}) aplay_opts+=(-f MPEG) format= rawformat= declare -a formats - ## throw psuedo random but non-audio data to aplay while capturing - ## error lines starting with `- ' to determine formats - aplay_out="$(echo ${PSEUDO_RANDOM_NOISE} | \ + if [[ "${alsa_if_type}" = "uo" ]]; then + ## uac type + interface_re="^[[:space:]]*Interface[[:space:]]([0-9])" + format_re="^[[:space:]]*Format:[[:space:]](.*)" + rates_re="^[[:space:]]*Rates:[[:space:]](.*)" + inside_interface= + format_found= + while read -r line; do + if [[ "${line}" =~ ${interface_re} ]]; then + ## new interface found + [[ ! -z ${DEBUG} ]] && \ + debug "interface found: ${BASH_REMATCH[1]}" + inside_interface=true + continue + fi + if [[ ! -z ${inside_interface} ]]; then + if [[ -z "${format_found}" ]]; then + if [[ "${line}" =~ ${format_re} ]]; then + ## format for interface found + format_found="${BASH_REMATCH[1]}" + [[ ! -z ${DEBUG} ]] && \ + debug "format found: \`${format_found}'" + continue + fi + else + if [[ "${line}" =~ ${rates_re} ]]; then + ## sample rates for interface/format found + inside_interface= + if [[ -z ${OPT_SAMPLERATES} ]]; then + formats+=("${format_found}") + format_found= + else + samplerates="${BASH_REMATCH[1]//,/Hz}Hz" + [[ ! -z ${DEBUG} ]] && \ + debug "sample rates found: \`${samplerates}'" + format="$(printf "%-18s" "${format_found}"): ${samplerates}" + formats+=("${format}") + fi + continue + fi + fi + fi + done<"${alsa_if_streamfile}" + + else + ## non-uac type: throw psuedo random but non-audio data to + ## aplay while capturing error lines starting with `- ' to + ## determine formats + aplay_out="$(echo ${PSEUDO_RANDOM_NOISE} | \ LANG=C ${CMD_APLAY} ${aplay_opts[@]} 2>&1 >/dev/null | grep '^- ')" - while read -r line; do - rawformat="${line/- /}" - if [[ -z ${OPT_SAMPLERATES} ]]; then - formats+=(${rawformat}) - else - declare -a sorted_rates - declare -a rates_supported - [[ ! -z ${DEBUG} ]] && \ - debug "#### try to get samplerates for format \`${rawformat}'" - rates_supported+=("$(return_alsa_samplerates \ + while read -r line; do + rawformat="${line/- /}" + if [[ -z ${OPT_SAMPLERATES} ]]; then + formats+=(${rawformat}) + else + declare -a sorted_rates + declare -a rates_supported + [[ ! -z ${DEBUG} ]] && \ + debug "#### try to get samplerates for format \`${rawformat}'" + rates_supported+=("$(return_alsa_samplerates \ "${alsa_if_hwaddress}" "${rawformat}")") - - IFS=$'\n' sorted_rates=($(sort -u -n <<<"${rates_supported[*]}")) - [[ ! -z ${DEBUG} ]] && \ - debug "#### sorted_rates returned: $(printf "%s " "${sorted_rates[@]}")" - rates="$(printf "%s " "${sorted_rates[@]}")" - format="$(printf "%-18s" "${rawformat}"): ${rates}" - formats+=(${format}) - fi - done <<< "${aplay_out}" - #formats+=("${format#\n}") - + + IFS=$'\n' sorted_rates=($(sort -u -n <<<"${rates_supported[*]}")) + [[ ! -z ${DEBUG} ]] && \ + debug "#### sorted_rates returned: $(printf "%s " "${sorted_rates[@]}")" + rates="$(printf "%s " "${sorted_rates[@]}")" + format="$(printf "%-18s" "${rawformat}"): ${rates}" + formats+=(${format}) + fi + done <<< "${aplay_out}" + #formats+=("${format#\n}") + + fi [[ ${#formats[@]} -gt 0 ]] && \ printf "%s\n" "${formats[@]}" || printf "${MSG_DEVICE_BUSY}" - + } @@ -734,7 +803,7 @@ function return_alsa_uac_ep() { ## 2: USB_DIR_IN: "IN|OUT", ## 3: USB_ENDPOINT_SYNCTYPE: "NONE|ASYNC|ADAPTIVE|SYNC" - alsa_streamfile_path="$1" + alsa_if_streamfile_path="$1" ep_mode="" ep_label_filter="Endpoint:" ep_label_regexp="^[[:space:]]*${ep_label_filter}" @@ -756,7 +825,7 @@ function return_alsa_uac_ep() { debug "matching endpoint found in line \`${line}': \`${ep_mode}'." break fi - done<"${alsa_streamfile_path}" + done<"${alsa_if_streamfile_path}" if [[ -z "${ep_mode}" ]]; then [[ ! -z ${DEBUG} ]] && \ @@ -1084,7 +1153,7 @@ MSG_ERROR_DEVICE_LOCKED="can't determine because device is locked" MSG_ERROR_DEVICE_BUSY="error: can't determine because device is in use" MSG_ERROR_DETERMINE_FORMATS="error: can't determine" MSG_ERROR_CHARDEV_NOFORMATS="can't determine: character device error" -MSG_ERROR_CHARDEV_NOT_ACCESSIBLE="error: not accessible" +MSG_ERROR_NOT_CHARDEV="error: is not a character device or not accessible" ## construct a list with the properties of the current ## interface if `OPT_QUIET' is not set MSG_ALSA_DEVNAME="device name"