raspberry-vanilla / android_local_manifest

105 stars 48 forks source link

USB Audio Input not working on RPI5 #99

Closed PiratedKukreja closed 1 month ago

PiratedKukreja commented 1 month ago

I'm trying to use USB microphone and USB sound card but it doesn't seem to work. I used wireless adb to look into alsa files and it does detect both the devices as capture devices but whenever I try to use any application or website to record sound or join calls on skype there is no input from the microphone.

jsln commented 1 month ago

Can you record audio with the alsa utilities?

I have a similar problem to you, I cannot get audio input from my HifiBerry DAC+ ADC Pro card with the applications, however when I try to use the alsa commands I can record mic input, with the caveat that I cannot actually use my microphone, it seems alsa does not let me configure the gain (I cannot find that control), but plugging an mp3 player in the mic input works, and I can play back the recorded audio:

alsa_arecord -f cd -D plughw:2,0 -d 5 rec.wav

also_aplay -D plughw:2,0 rec.wav

KonstaT commented 1 month ago

@PiratedKukreja Seems rather an application or user/hardware issue. I just tested USB audio input with Logitech USB webcam and it works fine.

@jsln Not quite the same issue. USB audio uses generic Android USB audio HAL (https://source.android.com/docs/core/audio/usb, https://github.com/raspberry-vanilla/android_device_brcm_rpi5/blob/android-14.0/device.mk#L28, https://github.com/raspberry-vanilla/android_device_brcm_rpi5/blob/android-14.0/audio/audio_policy_configuration.xml#L99). USB audio output/input works exactly the same as any other Android device.

Audio DACs on the other hand use device specific audio HAL implementation (https://github.com/raspberry-vanilla/android_device_brcm_rpi5/blob/android-14.0/audio/audio_hw.c) which doesn't support audio input in the first place since Raspberry Pi doesn't have any built-in hardware for audio input. It's rather simple to implement, as I've done it to support I2S MEMS mics, but can't be included by default.

Things working on Linux side (i.e. record using ALSA tools) doesn't really implicate much on Android side.

JSin commented 1 month ago

@jsln pinging you here due to a mistyping of an "i" instead of an "l"

KonstaT commented 1 month ago

@JSin Oops, sorry.

jsln commented 1 month ago

Thanks @KonstaT

I did not make clear in my previous comment that those alsa commands are working on my Android build (through adb shell). I can record on the command line.

Can you point me at your I2S MEMS mic implementation? Perhaps I can sort something out for the hifiberry card.

KonstaT commented 1 month ago

Can you point me at your I2S MEMS mic implementation? Perhaps I can sort something out for the hifiberry card.

diff --git a/audio/audio_hw.c b/audio/audio_hw.c
index 82f8167..6f50f2a 100644
--- a/audio/audio_hw.c
+++ b/audio/audio_hw.c
@@ -50,14 +50,22 @@
 /* number of frames per short period (low latency) */
 #define PERIOD_SIZE (CODEC_BASE_FRAME_COUNT * PERIOD_MULTIPLIER)
 /* number of pseudo periods for low latency playback */
-#define PLAYBACK_PERIOD_COUNT 4
-#define PLAYBACK_PERIOD_START_THRESHOLD 2
+#define PERIOD_COUNT 4
+#define PERIOD_START_THRESHOLD 2
 #define CODEC_SAMPLING_RATE 48000
 #define CHANNEL_STEREO 2
-#define MIN_WRITE_SLEEP_US      5000

-struct stub_stream_in {
+struct alsa_stream_in {
     struct audio_stream_in stream;
+
+    pthread_mutex_t lock;   /* see note below on mutex acquisition order */
+    struct pcm_config config;
+    struct pcm *pcm;
+    bool unavailable;
+    int standby;
+    struct alsa_audio_device *dev;
+    int read_threshold;
+    unsigned int read;
 };

 struct alsa_audio_device {
@@ -97,6 +105,20 @@ static int get_pcm_device()
     return atoi(device);
 }

+static int get_pcm_in_card()
+{
+    char card[PROPERTY_VALUE_MAX];
+    property_get("persist.audio.pcm.in.card", card, "0");
+    return atoi(card);
+}
+
+static int get_pcm_in_device()
+{
+    char device[PROPERTY_VALUE_MAX];
+    property_get("persist.audio.pcm.in.device", device, "0");
+    return atoi(device);
+}
+
 /* must be called with hw device and output stream mutexes locked */
 static int start_output_stream(struct alsa_stream_out *out)
 {
@@ -108,8 +130,8 @@ static int start_output_stream(struct alsa_stream_out *out)
     /* default to low power: will be corrected in out_write if necessary before first write to
      * tinyalsa.
      */
-    out->write_threshold = PLAYBACK_PERIOD_COUNT * PERIOD_SIZE;
-    out->config.start_threshold = PLAYBACK_PERIOD_START_THRESHOLD * PERIOD_SIZE;
+    out->write_threshold = PERIOD_COUNT * PERIOD_SIZE;
+    out->config.start_threshold = PERIOD_START_THRESHOLD * PERIOD_SIZE;
     out->config.avail_min = PERIOD_SIZE;

     out->pcm = pcm_open(get_pcm_card(), get_pcm_device(), PCM_OUT | PCM_MMAP | PCM_NOIRQ | PCM_MONOTONIC, &out->config);
@@ -134,13 +156,13 @@ static uint32_t out_get_sample_rate(const struct audio_stream *stream)

 static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate)
 {
-    ALOGV("out_set_sample_rate: %d", 0);
+    ALOGV("out_set_sample_rate: %d", rate);
     return -ENOSYS;
 }

 static size_t out_get_buffer_size(const struct audio_stream *stream)
 {
-    ALOGV("out_get_buffer_size: %d", 4096);
+    ALOGV("out_get_buffer_size");

     /* return the closest majoring multiple of 16 frames, as
      * audioflinger expects audio buffers to be a multiple of 16 frames */
@@ -165,7 +187,7 @@ static audio_format_t out_get_format(const struct audio_stream *stream)

 static int out_set_format(struct audio_stream *stream, audio_format_t format)
 {
-    ALOGV("out_set_format: %d",format);
+    ALOGV("out_set_format: %d", format);
     return -ENOSYS;
 }

@@ -245,7 +267,7 @@ static uint32_t out_get_latency(const struct audio_stream_out *stream)
 {
     ALOGV("out_get_latency");
     struct alsa_stream_out *out = (struct alsa_stream_out *)stream;
-    return (PERIOD_SIZE * PLAYBACK_PERIOD_COUNT * 1000) / out->config.rate;
+    return (PERIOD_SIZE * PERIOD_COUNT * 1000) / out->config.rate;
 }

 static int out_set_volume(struct audio_stream_out *stream, float left,
@@ -346,11 +368,40 @@ static int out_get_next_write_timestamp(const struct audio_stream_out *stream,
     return -EINVAL;
 }

+/* must be called with hw device and output stream mutexes locked */
+static int start_input_stream(struct alsa_stream_in *in)
+{
+    struct alsa_audio_device *adev = in->dev;
+
+    if (in->unavailable)
+        return -ENODEV;
+
+    /* default to low power: will be corrected in in_read if necessary before first read to
+     * tinyalsa.
+     */
+    in->read_threshold = PERIOD_COUNT * PERIOD_SIZE;
+    in->config.start_threshold = PERIOD_START_THRESHOLD * PERIOD_SIZE;
+    in->config.avail_min = PERIOD_SIZE;
+
+    in->pcm = pcm_open(get_pcm_in_card(), get_pcm_in_device(), PCM_IN, &in->config);
+
+    if (!pcm_is_ready(in->pcm)) {
+        ALOGE("cannot open pcm_in driver: %s", pcm_get_error(in->pcm));
+        pcm_close(in->pcm);
+        adev->active_output = NULL;
+        in->unavailable = true;
+        return -ENODEV;
+    }
+
+    adev->active_input = in;
+    return 0;
+}
+
 /** audio_stream_in implementation **/
 static uint32_t in_get_sample_rate(const struct audio_stream *stream)
 {
-    ALOGV("in_get_sample_rate");
-    return 8000;
+    struct alsa_stream_in *in = (struct alsa_stream_in *)stream;
+    return in->config.rate;
 }

 static int in_set_sample_rate(struct audio_stream *stream, uint32_t rate)
@@ -361,75 +412,142 @@ static int in_set_sample_rate(struct audio_stream *stream, uint32_t rate)

 static size_t in_get_buffer_size(const struct audio_stream *stream)
 {
-    ALOGV("in_get_buffer_size: %d", 320);
-    return 320;
+    ALOGV("in_get_buffer_size");
+
+    /* return the closest majoring multiple of 16 frames, as
+     * audioflinger expects audio buffers to be a multiple of 16 frames */
+    size_t size = PERIOD_SIZE;
+    size = ((size + 15) / 16) * 16;
+    return size * audio_stream_in_frame_size((struct audio_stream_in *)stream);
 }

 static audio_channel_mask_t in_get_channels(const struct audio_stream *stream)
 {
-    ALOGV("in_get_channels: %d", AUDIO_CHANNEL_IN_MONO);
-    return AUDIO_CHANNEL_IN_MONO;
+    ALOGV("in_get_channels");
+    struct alsa_stream_in *in = (struct alsa_stream_in *)stream;
+    return audio_channel_in_mask_from_count(in->config.channels);
 }

 static audio_format_t in_get_format(const struct audio_stream *stream)
 {
-    return AUDIO_FORMAT_PCM_16_BIT;
+    ALOGV("in_get_format");
+    struct alsa_stream_in *in = (struct alsa_stream_in *)stream;
+    return audio_format_from_pcm_format(in->config.format);
 }

 static int in_set_format(struct audio_stream *stream, audio_format_t format)
 {
+    ALOGV("in_set_format: %d", format);
     return -ENOSYS;
 }

-static int in_standby(struct audio_stream *stream)
+static int do_input_standby(struct alsa_stream_in *in)
 {
+    struct alsa_audio_device *adev = in->dev;
+
+    if (!in->standby) {
+        pcm_close(in->pcm);
+        in->pcm = NULL;
+        adev->active_input = NULL;
+        in->standby = 1;
+    }
     return 0;
 }

+static int in_standby(struct audio_stream *stream)
+{
+    ALOGV("in_standby");
+    struct alsa_stream_in *in = (struct alsa_stream_in *)stream;
+    int status;
+
+    pthread_mutex_lock(&in->dev->lock);
+    pthread_mutex_lock(&in->lock);
+    status = do_input_standby(in);
+    pthread_mutex_unlock(&in->lock);
+    pthread_mutex_unlock(&in->dev->lock);
+    return status;
+}
+
 static int in_dump(const struct audio_stream *stream, int fd)
 {
+    ALOGV("in_dump");
     return 0;
 }

 static int in_set_parameters(struct audio_stream *stream, const char *kvpairs)
 {
+    ALOGV("in_set_parameters");
     return 0;
 }

-static char * in_get_parameters(const struct audio_stream *stream,
-        const char *keys)
+static char * in_get_parameters(const struct audio_stream *stream, const char *keys)
 {
+    ALOGV("in_get_parameters");
     return strdup("");
 }

 static int in_set_gain(struct audio_stream_in *stream, float gain)
 {
+    ALOGV("in_set_gain");
     return 0;
 }

 static ssize_t in_read(struct audio_stream_in *stream, void* buffer,
         size_t bytes)
 {
-    ALOGV("in_read: bytes %zu", bytes);
-    /* XXX: fake timing for audio input */
-    usleep((int64_t)bytes * 1000000 / audio_stream_in_frame_size(stream) /
-            in_get_sample_rate(&stream->common));
-    memset(buffer, 0, bytes);
+    int ret;
+    struct alsa_stream_in *in = (struct alsa_stream_in *)stream;
+    struct alsa_audio_device *adev = in->dev;
+    size_t frame_size = audio_stream_in_frame_size(stream);
+    size_t in_frames = bytes / frame_size;
+
+    /* acquiring hw device mutex systematically is useful if a low priority thread is waiting
+     * on the output stream mutex - e.g. executing select_mode() while holding the hw device
+     * mutex
+     */
+    pthread_mutex_lock(&adev->lock);
+    pthread_mutex_lock(&in->lock);
+    if (in->standby) {
+        ret = start_input_stream(in);
+        if (ret != 0) {
+            pthread_mutex_unlock(&adev->lock);
+            goto exit;
+        }
+        in->standby = 0;
+    }
+
+    pthread_mutex_unlock(&adev->lock);
+
+    ret = pcm_read(in->pcm, buffer, in_frames * frame_size);
+    if (ret == 0) {
+        in->read += in_frames;
+    }
+exit:
+    pthread_mutex_unlock(&in->lock);
+
+    if (ret != 0) {
+        usleep((int64_t)bytes * 1000000 / audio_stream_in_frame_size(stream) /
+                in_get_sample_rate(&stream->common));
+    }
+
     return bytes;
 }

 static uint32_t in_get_input_frames_lost(struct audio_stream_in *stream)
 {
+    ALOGV("in_get_input_frames_lost");
     return 0;
 }

 static int in_add_audio_effect(const struct audio_stream *stream, effect_handle_t effect)
 {
+    ALOGV("in_add_audio_effect: %p", effect);
     return 0;
 }

 static int in_remove_audio_effect(const struct audio_stream *stream, effect_handle_t effect)
 {
+    ALOGV("in_remove_audio_effect: %p", effect);
     return 0;
 }

@@ -479,7 +597,7 @@ static int adev_open_output_stream(struct audio_hw_device *dev,
     out->config.rate = CODEC_SAMPLING_RATE;
     out->config.format = PCM_FORMAT_S16_LE;
     out->config.period_size = PERIOD_SIZE;
-    out->config.period_count = PLAYBACK_PERIOD_COUNT;
+    out->config.period_count = PERIOD_COUNT;

     if (out->config.rate != config->sample_rate ||
            audio_channel_count_from_out_mask(config->channel_mask) != CHANNEL_STEREO ||
@@ -573,14 +691,22 @@ static int adev_set_mode(struct audio_hw_device *dev, audio_mode_t mode)

 static int adev_set_mic_mute(struct audio_hw_device *dev, bool state)
 {
-    ALOGV("adev_set_mic_mute: %d",state);
-    return -ENOSYS;
+    ALOGV("adev_set_mic_mute: %d", state);
+    struct alsa_audio_device *adev = (struct alsa_audio_device *)dev;
+    pthread_mutex_lock(&adev->lock);
+    adev->mic_mute = state;
+    pthread_mutex_unlock(&adev->lock);
+    return 0;
 }

 static int adev_get_mic_mute(const struct audio_hw_device *dev, bool *state)
 {
     ALOGV("adev_get_mic_mute");
-    return -ENOSYS;
+    struct alsa_audio_device *adev = (struct alsa_audio_device *)dev;
+    pthread_mutex_lock(&adev->lock);
+    *state = adev->mic_mute;
+    pthread_mutex_unlock(&adev->lock);
+    return 0;
 }

 static size_t adev_get_input_buffer_size(const struct audio_hw_device *dev,
@@ -590,20 +716,27 @@ static size_t adev_get_input_buffer_size(const struct audio_hw_device *dev,
     return 320;
 }

-static int adev_open_input_stream(struct audio_hw_device __unused *dev,
+static int adev_open_input_stream(struct audio_hw_device *dev,
         audio_io_handle_t handle,
         audio_devices_t devices,
         struct audio_config *config,
         struct audio_stream_in **stream_in,
-        audio_input_flags_t flags __unused,
+        audio_input_flags_t flags,
         const char *address __unused,
         audio_source_t source __unused)
 {
-    struct stub_stream_in *in;
-
     ALOGV("adev_open_input_stream...");

-    in = (struct stub_stream_in *)calloc(1, sizeof(struct stub_stream_in));
+    struct alsa_audio_device *ladev = (struct alsa_audio_device *)dev;
+    struct alsa_stream_in *in;
+    struct pcm_params *params;
+    int ret = 0;
+
+    params = pcm_params_get(get_pcm_in_card(), get_pcm_in_device(), PCM_IN);
+    if (!params)
+        return -ENOSYS;
+
+    in = (struct alsa_stream_in *)calloc(1, sizeof(struct alsa_stream_in));
     if (!in)
         return -ENOMEM;

@@ -623,15 +756,45 @@ static int adev_open_input_stream(struct audio_hw_device __unused *dev,
     in->stream.read = in_read;
     in->stream.get_input_frames_lost = in_get_input_frames_lost;

+    in->config.channels = CHANNEL_STEREO;
+    in->config.rate = CODEC_SAMPLING_RATE;
+    in->config.format = PCM_FORMAT_S16_LE;
+    in->config.period_size = PERIOD_SIZE;
+    in->config.period_count = PERIOD_COUNT;
+
+    if (in->config.rate != config->sample_rate ||
+           audio_channel_count_from_in_mask(config->channel_mask) != CHANNEL_STEREO ||
+               in->config.format !=  pcm_format_from_audio_format(config->format) ) {
+        config->sample_rate = in->config.rate;
+        config->format = audio_format_from_pcm_format(in->config.format);
+        config->channel_mask = audio_channel_in_mask_from_count(CHANNEL_STEREO);
+        ret = -EINVAL;
+    }
+
+    ALOGI("adev_open_input_stream selects channels=%d rate=%d format=%d",
+                in->config.channels, in->config.rate, in->config.format);
+
+    in->dev = ladev;
+    in->standby = 1;
+    in->unavailable = false;
+
+    config->format = in_get_format(&in->stream.common);
+    config->channel_mask = in_get_channels(&in->stream.common);
+    config->sample_rate = in_get_sample_rate(&in->stream.common);
+
     *stream_in = &in->stream;
-    return 0;
+
+    /* TODO The retry mechanism isn't implemented in AudioPolicyManager/AudioFlinger. */
+    ret = 0;
+
+    return ret;
 }

 static void adev_close_input_stream(struct audio_hw_device *dev,
-        struct audio_stream_in *in)
+        struct audio_stream_in *stream)
 {
     ALOGV("adev_close_input_stream...");
-    return;
+    free(stream);
 }

 static int adev_dump(const audio_hw_device_t *device, int fd)
diff --git a/audio/audio_policy_configuration.xml b/audio/audio_policy_configuration.xml
index b2647e8..337df49 100644
--- a/audio/audio_policy_configuration.xml
+++ b/audio/audio_policy_configuration.xml
@@ -63,12 +63,12 @@
                 </devicePort>
                 <devicePort tagName="Built-In Mic" type="AUDIO_DEVICE_IN_BUILTIN_MIC" role="source">
                     <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
-                             samplingRates="8000 11025 12000 16000 22050 24000 32000 44100 48000"
+                             samplingRates="48000"
                              channelMasks="AUDIO_CHANNEL_IN_MONO AUDIO_CHANNEL_IN_STEREO"/>
                 </devicePort>
                 <devicePort tagName="Wired Headset Mic" type="AUDIO_DEVICE_IN_WIRED_HEADSET" role="source">
                     <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
-                             samplingRates="8000 11025 12000 16000 22050 24000 32000 44100 48000"
+                             samplingRates="48000"
                              channelMasks="AUDIO_CHANNEL_IN_MONO AUDIO_CHANNEL_IN_STEREO"/>
                 </devicePort>
                 <devicePort tagName="BT SCO Headset Mic" type="AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET" role="source">
PiratedKukreja commented 1 month ago

Hello @jsln I didn't try recording audio using alsa utilities.

@KonstaT I tried using a USB Logitech web cam () and I still can't get this to work, devices i've tried until now are a generic sound card, a usb microphone, a Logitech web cam, bluetooth headsets. Software i've tried using are edge browser (mictests.com), Discord, Skype and maybe a few random sound recorders.

PS: IDK if this is helpful but I've an orange pi too and all the above mentioned devices seem to work with it.