Home | History | Annotate | Download | only in alsa
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.internal.alsa;
     17 
     18 import android.util.Slog;
     19 import java.io.BufferedReader;
     20 import java.io.File;
     21 import java.io.FileNotFoundException;
     22 import java.io.FileReader;
     23 import java.io.IOException;
     24 import java.util.ArrayList;
     25 
     26 /**
     27  * @hide
     28  * Retrieves information from an ALSA "devices" file.
     29  */
     30 public class AlsaDevicesParser {
     31     private static final String TAG = "AlsaDevicesParser";
     32     protected static final boolean DEBUG = false;
     33 
     34     private static final String kDevicesFilePath = "/proc/asound/devices";
     35 
     36     private static final int kIndex_CardDeviceField = 5;
     37     private static final int kStartIndex_CardNum = 6;
     38     private static final int kEndIndex_CardNum = 8; // one past
     39     private static final int kStartIndex_DeviceNum = 9;
     40     private static final int kEndIndex_DeviceNum = 11; // one past
     41     private static final int kStartIndex_Type = 14;
     42 
     43     private static LineTokenizer mTokenizer = new LineTokenizer(" :[]-");
     44 
     45     private boolean mHasCaptureDevices = false;
     46     private boolean mHasPlaybackDevices = false;
     47     private boolean mHasMIDIDevices = false;
     48 
     49     public class AlsaDeviceRecord {
     50         public static final int kDeviceType_Unknown = -1;
     51         public static final int kDeviceType_Audio = 0;
     52         public static final int kDeviceType_Control = 1;
     53         public static final int kDeviceType_MIDI = 2;
     54 
     55         public static final int kDeviceDir_Unknown = -1;
     56         public static final int kDeviceDir_Capture = 0;
     57         public static final int kDeviceDir_Playback = 1;
     58 
     59         int mCardNum = -1;
     60         int mDeviceNum = -1;
     61         int mDeviceType = kDeviceType_Unknown;
     62         int mDeviceDir = kDeviceDir_Unknown;
     63 
     64         public AlsaDeviceRecord() {}
     65 
     66         public boolean parse(String line) {
     67             // "0123456789012345678901234567890"
     68             // "  2: [ 0-31]: digital audio playback"
     69             // "  3: [ 0-30]: digital audio capture"
     70             // " 35: [ 1]   : control"
     71             // " 36: [ 2- 0]: raw midi"
     72 
     73             final int kToken_LineNum = 0;
     74             final int kToken_CardNum = 1;
     75             final int kToken_DeviceNum = 2;
     76             final int kToken_Type0 = 3; // "digital", "control", "raw"
     77             final int kToken_Type1 = 4; // "audio", "midi"
     78             final int kToken_Type2 = 5; // "capture", "playback"
     79 
     80             int tokenOffset = 0;
     81             int delimOffset = 0;
     82             int tokenIndex = kToken_LineNum;
     83             while (true) {
     84                 tokenOffset = mTokenizer.nextToken(line, delimOffset);
     85                 if (tokenOffset == LineTokenizer.kTokenNotFound) {
     86                     break; // bail
     87                 }
     88                 delimOffset = mTokenizer.nextDelimiter(line, tokenOffset);
     89                 if (delimOffset == LineTokenizer.kTokenNotFound) {
     90                     delimOffset = line.length();
     91                 }
     92                 String token = line.substring(tokenOffset, delimOffset);
     93 
     94                 try {
     95                     switch (tokenIndex) {
     96                     case kToken_LineNum:
     97                         // ignore
     98                         break;
     99 
    100                     case kToken_CardNum:
    101                         mCardNum = Integer.parseInt(token);
    102                         if (line.charAt(delimOffset) != '-') {
    103                             tokenIndex++; // no device # in the token stream
    104                         }
    105                         break;
    106 
    107                     case kToken_DeviceNum:
    108                         mDeviceNum = Integer.parseInt(token);
    109                         break;
    110 
    111                     case kToken_Type0:
    112                         if (token.equals("digital")) {
    113                             // NOP
    114                         } else if (token.equals("control")) {
    115                             mDeviceType = kDeviceType_Control;
    116                         } else if (token.equals("raw")) {
    117                             // NOP
    118                         }
    119                         break;
    120 
    121                     case kToken_Type1:
    122                         if (token.equals("audio")) {
    123                             mDeviceType = kDeviceType_Audio;
    124                         } else if (token.equals("midi")) {
    125                             mDeviceType = kDeviceType_MIDI;
    126                             mHasMIDIDevices = true;
    127                         }
    128                         break;
    129 
    130                     case kToken_Type2:
    131                         if (token.equals("capture")) {
    132                             mDeviceDir = kDeviceDir_Capture;
    133                             mHasCaptureDevices = true;
    134                         } else if (token.equals("playback")) {
    135                             mDeviceDir = kDeviceDir_Playback;
    136                             mHasPlaybackDevices = true;
    137                         }
    138                         break;
    139                     } // switch (tokenIndex)
    140                 } catch (NumberFormatException e) {
    141                     Slog.e(TAG, "Failed to parse token " + tokenIndex + " of " + kDevicesFilePath
    142                         + " token: " + token);
    143                     return false;
    144                 }
    145 
    146                 tokenIndex++;
    147             } // while (true)
    148 
    149             return true;
    150         } // parse()
    151 
    152         public String textFormat() {
    153             StringBuilder sb = new StringBuilder();
    154             sb.append("[" + mCardNum + ":" + mDeviceNum + "]");
    155 
    156             switch (mDeviceType) {
    157             case kDeviceType_Unknown:
    158                 sb.append(" N/A");
    159                 break;
    160             case kDeviceType_Audio:
    161                 sb.append(" Audio");
    162                 break;
    163             case kDeviceType_Control:
    164                 sb.append(" Control");
    165                 break;
    166             case kDeviceType_MIDI:
    167                 sb.append(" MIDI");
    168                 break;
    169             }
    170 
    171             switch (mDeviceDir) {
    172             case kDeviceDir_Unknown:
    173                 sb.append(" N/A");
    174                 break;
    175             case kDeviceDir_Capture:
    176                 sb.append(" Capture");
    177                 break;
    178             case kDeviceDir_Playback:
    179                 sb.append(" Playback");
    180                 break;
    181             }
    182 
    183             return sb.toString();
    184         }
    185     }
    186 
    187     private ArrayList<AlsaDeviceRecord> mDeviceRecords = new ArrayList<AlsaDeviceRecord>();
    188 
    189     public AlsaDevicesParser() {}
    190 
    191     //
    192     // Access
    193     //
    194     public int getDefaultDeviceNum(int card) {
    195         // TODO - This (obviously) isn't sufficient. Revisit.
    196         return 0;
    197     }
    198 
    199     //
    200     // Predicates
    201     //
    202    public boolean hasPlaybackDevices() {
    203         return mHasPlaybackDevices;
    204     }
    205 
    206     public boolean hasPlaybackDevices(int card) {
    207         for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
    208             if (deviceRecord.mCardNum == card &&
    209                 deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio &&
    210                 deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Playback) {
    211                 return true;
    212             }
    213         }
    214         return false;
    215     }
    216 
    217     public boolean hasCaptureDevices() {
    218         return mHasCaptureDevices;
    219     }
    220 
    221     public boolean hasCaptureDevices(int card) {
    222         for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
    223             if (deviceRecord.mCardNum == card &&
    224                 deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio &&
    225                 deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Capture) {
    226                 return true;
    227             }
    228         }
    229         return false;
    230     }
    231 
    232     public boolean hasMIDIDevices() {
    233         return mHasMIDIDevices;
    234     }
    235 
    236     public boolean hasMIDIDevices(int card) {
    237         for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
    238             if (deviceRecord.mCardNum == card &&
    239                 deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_MIDI) {
    240                 return true;
    241             }
    242         }
    243         return false;
    244     }
    245 
    246     //
    247     // Process
    248     //
    249     private boolean isLineDeviceRecord(String line) {
    250         return line.charAt(kIndex_CardDeviceField) == '[';
    251     }
    252 
    253     public void scan() {
    254         mDeviceRecords.clear();
    255 
    256         File devicesFile = new File(kDevicesFilePath);
    257         try {
    258             FileReader reader = new FileReader(devicesFile);
    259             BufferedReader bufferedReader = new BufferedReader(reader);
    260             String line = "";
    261             while ((line = bufferedReader.readLine()) != null) {
    262                 if (isLineDeviceRecord(line)) {
    263                     AlsaDeviceRecord deviceRecord = new AlsaDeviceRecord();
    264                     deviceRecord.parse(line);
    265                     mDeviceRecords.add(deviceRecord);
    266                 }
    267             }
    268             reader.close();
    269         } catch (FileNotFoundException e) {
    270             e.printStackTrace();
    271         } catch (IOException e) {
    272             e.printStackTrace();
    273         }
    274     }
    275 
    276     //
    277     // Loging
    278     //
    279     public void Log(String heading) {
    280         if (DEBUG) {
    281             Slog.i(TAG, heading);
    282             for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
    283                 Slog.i(TAG, deviceRecord.textFormat());
    284             }
    285         }
    286     }
    287 } // class AlsaDevicesParser
    288 
    289