Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8640be25 authored by Paul McLean's avatar Paul McLean Committed by Android Git Automerger
Browse files

am 6f127c9b: Merge "Initial implementation of USB Audio output."

* commit '6f127c9b':
  Initial implementation of USB Audio output.
parents e954053f 6f127c9b
Loading
Loading
Loading
Loading
+116 −0
Original line number Diff line number Diff line
/*
 * 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.
 */

package com.android.alsascan;

import android.util.Slog;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Vector;

/**
 * @hide Retrieves information from an ALSA "cards" file.
 */
public class AlsaCardsParser {
    private static final String TAG = "AlsaCardsParser";

    private static LineTokenizer tokenizer_ = new LineTokenizer(" :[]");

    public class AlsaCardRecord {
        public int mCardNum = -1;
        public String mField1 = "";
        public String mCardName = "";
        public String mCardDescription = "";

        public AlsaCardRecord() {}

        public boolean parse(String line, int lineIndex) {
            int tokenIndex = 0;
            int delimIndex = 0;
            if (lineIndex == 0) {
                // line # (skip)
                tokenIndex = tokenizer_.nextToken(line, tokenIndex);
                delimIndex = tokenizer_.nextDelimiter(line, tokenIndex);

                // mField1
                tokenIndex = tokenizer_.nextToken(line, delimIndex);
                delimIndex = tokenizer_.nextDelimiter(line, tokenIndex);
                mField1 = line.substring(tokenIndex, delimIndex);

                // mCardName
                tokenIndex = tokenizer_.nextToken(line, delimIndex);
                // delimIndex = tokenizer_.nextDelimiter(line, tokenIndex);
                mCardName = line.substring(tokenIndex);
                // done
              } else if (lineIndex == 1) {
                  tokenIndex = tokenizer_.nextToken(line, 0);
                  if (tokenIndex != -1) {
                      mCardDescription = line.substring(tokenIndex);
                  }
            }

            return true;
        }

        public String textFormat() {
          return mCardName + " : " + mCardDescription;
        }
    }

    private Vector<AlsaCardRecord> cardRecords_ = new Vector<AlsaCardRecord>();

    public void scan() {
          cardRecords_.clear();
          final String cardsFilePath = "/proc/asound/cards";
          File cardsFile = new File(cardsFilePath);
          try {
              FileReader reader = new FileReader(cardsFile);
              BufferedReader bufferedReader = new BufferedReader(reader);
              String line = "";
              while ((line = bufferedReader.readLine()) != null) {
                  AlsaCardRecord cardRecord = new AlsaCardRecord();
                  cardRecord.parse(line, 0);
                  cardRecord.parse(line = bufferedReader.readLine(), 1);
                  cardRecords_.add(cardRecord);
              }
              reader.close();
          } catch (FileNotFoundException e) {
              e.printStackTrace();
          } catch (IOException e) {
              e.printStackTrace();
          }
      }

      public AlsaCardRecord getCardRecordAt(int index) {
          return cardRecords_.get(index);
      }

      public int getNumCardRecords() {
          return cardRecords_.size();
      }

    public void Log() {
      int numCardRecs = getNumCardRecords();
      for (int index = 0; index < numCardRecs; ++index) {
          Slog.w(TAG, "usb:" + getCardRecordAt(index).textFormat());
      }
    }

    public AlsaCardsParser() {}
}
+240 −0
Original line number Diff line number Diff line
/*
 * 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.
 */
package com.android.alsascan;

import android.util.Slog;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Vector;

/**
 * @hide
 * Retrieves information from an ALSA "devices" file.
 */
public class AlsaDevicesParser {
    private static final String TAG = "AlsaDevicesParser";

    private static final int kIndex_CardDeviceField = 5;
    private static final int kStartIndex_CardNum = 6;
    private static final int kEndIndex_CardNum = 8; // one past
    private static final int kStartIndex_DeviceNum = 9;
    private static final int kEndIndex_DeviceNum = 11; // one past
    private static final int kStartIndex_Type = 14;

    private static LineTokenizer mTokenizer = new LineTokenizer(" :[]-");

    private boolean mHasCaptureDevices = false;
    private boolean mHasPlaybackDevices = false;
    private boolean mHasMIDIDevices = false;

    public class AlsaDeviceRecord {
        public static final int kDeviceType_Unknown = -1;
        public static final int kDeviceType_Audio = 0;
        public static final int kDeviceType_Control = 1;
        public static final int kDeviceType_MIDI = 2;

        public static final int kDeviceDir_Unknown = -1;
        public static final int kDeviceDir_Capture = 0;
        public static final int kDeviceDir_Playback = 1;

        int mCardNum = -1;
        int mDeviceNum = -1;
        int mDeviceType = kDeviceType_Unknown;
        int mDeviceDir = kDeviceDir_Unknown;

        public AlsaDeviceRecord() {
        }

        public boolean parse(String line) {
            // "0123456789012345678901234567890"
            // "  2: [ 0-31]: digital audio playback"
            // "  3: [ 0-30]: digital audio capture"
            // " 35: [ 1]   : control"
            // " 36: [ 2- 0]: raw midi"

            final int kToken_LineNum = 0;
            final int kToken_CardNum = 1;
            final int kToken_DeviceNum = 2;
            final int kToken_Type0 = 3; // "digital", "control", "raw"
            final int kToken_Type1 = 4; // "audio", "midi"
            final int kToken_Type2 = 5; // "capture", "playback"

            int tokenOffset = 0;
            int delimOffset = 0;
            int tokenIndex = kToken_LineNum;
            while (true) {
                tokenOffset = mTokenizer.nextToken(line, delimOffset);
                if (tokenOffset == LineTokenizer.kTokenNotFound) {
                    break; // bail
                }
                delimOffset = mTokenizer.nextDelimiter(line, tokenOffset);
                if (delimOffset == LineTokenizer.kTokenNotFound) {
                    delimOffset = line.length();
                }
                String token = line.substring(tokenOffset, delimOffset);

                switch (tokenIndex) {
                case kToken_LineNum:
                    // ignore
                    break;

                case kToken_CardNum:
                    mCardNum = Integer.parseInt(token);
                    if (line.charAt(delimOffset) != '-') {
                        tokenIndex++; // no device # in the token stream
                    }
                    break;

                case kToken_DeviceNum:
                    mDeviceNum = Integer.parseInt(token);
                    break;

                case kToken_Type0:
                    if (token.equals("digital")) {
                        // NOP
                    } else if (token.equals("control")) {
                        mDeviceType = kDeviceType_Control;
                    } else if (token.equals("raw")) {
                        // NOP
                    }
                    break;

                case kToken_Type1:
                    if (token.equals("audio")) {
                        mDeviceType = kDeviceType_Audio;
                    } else if (token.equals("midi")) {
                        mDeviceType = kDeviceType_MIDI;
                        mHasMIDIDevices = true;
                    }
                    break;

                case kToken_Type2:
                    if (token.equals("capture")) {
                        mDeviceDir = kDeviceDir_Capture;
                        mHasCaptureDevices = true;
                    } else if (token.equals("playback")) {
                        mDeviceDir = kDeviceDir_Playback;
                        mHasPlaybackDevices = true;
                    }
                    break;
                } // switch (tokenIndex)

                tokenIndex++;
            } // while (true)

            return true;
        } // parse()

        public String textFormat() {
            StringBuilder sb = new StringBuilder();
            sb.append("[" + mCardNum + ":" + mDeviceNum + "]");

            switch (mDeviceType) {
            case kDeviceType_Unknown:
                sb.append(" N/A");
                break;
            case kDeviceType_Audio:
                sb.append(" Audio");
                break;
            case kDeviceType_Control:
                sb.append(" Control");
                break;
            case kDeviceType_MIDI:
                sb.append(" MIDI");
                break;
            }

            switch (mDeviceDir) {
            case kDeviceDir_Unknown:
                sb.append(" N/A");
                break;
            case kDeviceDir_Capture:
                sb.append(" Capture");
                break;
            case kDeviceDir_Playback:
                sb.append(" Playback");
                break;
            }

            return sb.toString();
        }
    }

    private Vector<AlsaDeviceRecord>
            deviceRecords_ = new Vector<AlsaDeviceRecord>();

    private boolean isLineDeviceRecord(String line) {
        return line.charAt(kIndex_CardDeviceField) == '[';
    }

    public AlsaDevicesParser() {
    }

    public int getNumDeviceRecords() {
        return deviceRecords_.size();
    }

    public AlsaDeviceRecord getDeviceRecordAt(int index) {
        return deviceRecords_.get(index);
    }

    public void Log() {
        int numDevRecs = getNumDeviceRecords();
        for (int index = 0; index < numDevRecs; ++index) {
            Slog.w(TAG, "usb:" + getDeviceRecordAt(index).textFormat());
        }
    }

    public boolean hasPlaybackDevices() {
        return mHasPlaybackDevices;
    }

    public boolean hasCaptureDevices() {
        return mHasCaptureDevices;
    }

    public boolean hasMIDIDevices() {
        return mHasMIDIDevices;
    }

    public void scan() {
        deviceRecords_.clear();

        final String devicesFilePath = "/proc/asound/devices";
        File devicesFile = new File(devicesFilePath);
        try {
            FileReader reader = new FileReader(devicesFile);
            BufferedReader bufferedReader = new BufferedReader(reader);
            String line = "";
            while ((line = bufferedReader.readLine()) != null) {
                if (isLineDeviceRecord(line)) {
                    AlsaDeviceRecord deviceRecord = new AlsaDeviceRecord();
                    deviceRecord.parse(line);
                    deviceRecords_.add(deviceRecord);
                }
            }
            reader.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
} // class AlsaDevicesParser
+57 −0
Original line number Diff line number Diff line
/*
 * 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.
 */
package com.android.alsascan;

/**
 * @hide
 * Breaks lines in an ALSA "cards" or "devices" file into tokens.
 * TODO(pmclean) Look into replacing this with String.split().
 */
public class LineTokenizer {
    public static final int kTokenNotFound = -1;

    private String mDelimiters = "";

    public LineTokenizer(String delimiters) {
        mDelimiters = delimiters;
    }

    int nextToken(String line, int startIndex) {
        int len = line.length();
        int offset = startIndex;
        for (; offset < len; offset++) {
            if (mDelimiters.indexOf(line.charAt(offset)) == -1) {
                // past a delimiter
                break;
            }
      }

      return offset < len ? offset : kTokenNotFound;
    }

    int nextDelimiter(String line, int startIndex) {
        int len = line.length();
        int offset = startIndex;
        for (; offset < len; offset++) {
            if (mDelimiters.indexOf(line.charAt(offset)) != -1) {
                // past a delimiter
                break;
            }
        }

      return offset < len ? offset : kTokenNotFound;
    }
}
+23 −7
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
import android.hardware.usb.UsbManager;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.os.Binder;
@@ -528,6 +529,7 @@ public class AudioService extends IAudioService.Stub {
        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
        intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);

        intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
        // TODO merge orientation and rotation
@@ -3975,7 +3977,8 @@ public class AudioService extends IAudioService.Stub {
                    (device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE))) {
                setBluetoothA2dpOnInt(true);
            }
            boolean isUsb = ((device & AudioSystem.DEVICE_OUT_ALL_USB) != 0);
            boolean isUsb = ((device & AudioSystem.DEVICE_OUT_ALL_USB) != 0) ||
                            ((device & AudioSystem.DEVICE_IN_ALL_USB) != 0);
            handleDeviceConnection((state == 1), device, (isUsb ? name : ""));
            if (state != 0) {
                if ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET) ||
@@ -4083,18 +4086,31 @@ public class AudioService extends IAudioService.Stub {
            } else if (action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) ||
                           action.equals(Intent.ACTION_USB_AUDIO_DEVICE_PLUG)) {
                state = intent.getIntExtra("state", 0);

                int alsaCard = intent.getIntExtra("card", -1);
                int alsaDevice = intent.getIntExtra("device", -1);
                boolean hasPlayback = intent.getBooleanExtra("hasPlayback", false);
                boolean hasCapture = intent.getBooleanExtra("hasCapture", false);
                boolean hasMIDI = intent.getBooleanExtra("hasMIDI", false);

                String params = (alsaCard == -1 && alsaDevice == -1 ? ""
                                    : "card=" + alsaCard + ";device=" + alsaDevice);

                //TODO(pmclean) - Ignore for now the hasPlayback & hasCapture flags since we
                // still need to call setWiredDeviceConnectionState() on disconnect (when we
                // don't have card/device files to parse for this info). We will need to store
                // that info here when we get smarter about multiple USB card/devices.
                // Playback Device
                device = action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) ?
                        AudioSystem.DEVICE_OUT_USB_ACCESSORY : AudioSystem.DEVICE_OUT_USB_DEVICE;
                Log.v(TAG, "Broadcast Receiver: Got "
                        + (action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) ?
                              "ACTION_USB_AUDIO_ACCESSORY_PLUG" : "ACTION_USB_AUDIO_DEVICE_PLUG")
                        + ", state = " + state + ", card: " + alsaCard + ", device: " + alsaDevice);
                setWiredDeviceConnectionState(device, state, params);
            } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {

                // Capture Device
                device = action.equals(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG) ?
                    AudioSystem.DEVICE_IN_USB_ACCESSORY : AudioSystem.DEVICE_IN_USB_DEVICE;
                setWiredDeviceConnectionState(device, state, params);
            }
            else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
                boolean broadcast = false;
                int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
                synchronized (mScoClients) {
@@ -4199,7 +4215,7 @@ public class AudioService extends IAudioService.Stub {
                        mStreamStates[AudioSystem.STREAM_MUSIC], 0);
            }
        }
    }
    } // end class AudioServiceBroadcastReceiver

    //==========================================================================================
    // RemoteControlDisplay / RemoteControlClient / Remote info
+2 −0
Original line number Diff line number Diff line
@@ -288,6 +288,8 @@ public class AudioSystem
                                             DEVICE_IN_USB_DEVICE |
                                             DEVICE_IN_DEFAULT);
    public static final int DEVICE_IN_ALL_SCO = DEVICE_IN_BLUETOOTH_SCO_HEADSET;
    public static final int DEVICE_IN_ALL_USB = (DEVICE_IN_USB_ACCESSORY |
                                                 DEVICE_IN_USB_DEVICE);

    // device states, must match AudioSystem::device_connection_state
    public static final int DEVICE_STATE_UNAVAILABLE = 0;
Loading