gen2brain / malgo

Mini audio library
The Unlicense
288 stars 49 forks source link

Unable to capture from specific device #56

Open navinreddy23 opened 1 month ago

navinreddy23 commented 1 month ago

Hi,

I am unable to capture from a specific device, but I am able to play back. The code structure remains the same and is derived from the examples.

package main

import (
    "fmt"
    "log"
    "os"

    "github.com/gen2brain/malgo"
)

func main() {
    context, err := malgo.InitContext(nil, malgo.ContextConfig{}, nil)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    defer func() {
        _ = context.Uninit()
        context.Free()
    }()

    // Capture devices.
    infos, err := context.Devices(malgo.Capture)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    fmt.Println("Capture Devices")
    for i, info := range infos {
        e := "ok"
        full, err := context.DeviceInfo(malgo.Capture, info.ID, malgo.Shared)
        if err != nil {
            e = err.Error()
        }
        fmt.Printf("    ID: [%d]: %v, %s, [%s], formats: %+v\n",
            i, info.ID, info.Name(), e, full.Formats)
    }

    id := scanIdOption(infos)

    fmt.Printf("Reading audio from: %s\n", infos[id].Name())

    deviceConfig := malgo.DefaultDeviceConfig(malgo.Capture)
    deviceConfig.Capture.Format = malgo.FormatS16
    deviceConfig.Capture.Channels = 1
    deviceConfig.Playback.Format = malgo.FormatS16
    deviceConfig.Playback.Channels = 1
    deviceConfig.SampleRate = 16000
    deviceConfig.Alsa.NoMMap = 1
    deviceConfig.PeriodSizeInMilliseconds = 40
    deviceConfig.Capture.DeviceID = infos[id].ID.Pointer()

    var playbackSampleCount uint32
    var capturedSampleCount uint32
    pCapturedSamples := make([]byte, 0)

    sizeInBytes := uint32(malgo.SampleSizeInBytes(deviceConfig.Capture.Format))
    onRecvFrames := func(pSample2, pSample []byte, framecount uint32) {

        sampleCount := framecount * deviceConfig.Capture.Channels * sizeInBytes
        log.Println(framecount)

        newCapturedSampleCount := capturedSampleCount + sampleCount

        pCapturedSamples = append(pCapturedSamples, pSample...)

        capturedSampleCount = newCapturedSampleCount

    }

    fmt.Println("Recording...")
    captureCallbacks := malgo.DeviceCallbacks{
        Data: onRecvFrames,
    }
    device, err := malgo.InitDevice(context.Context, deviceConfig, captureCallbacks)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    err = device.Start()
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    fmt.Println("Press Enter to stop recording...")
    fmt.Scanln()

    device.Uninit()

    infos, err = context.Devices(malgo.Playback)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    fmt.Println("Playback Devices")
    for i, info := range infos {
        e := "ok"
        full, err := context.DeviceInfo(malgo.Playback, info.ID, malgo.Shared)
        if err != nil {
            e = err.Error()
        }
        fmt.Printf("    %d: %v, %s, [%s], formats: %+v\n",
            i, info.ID, info.Name(), e, full.Formats)
    }

    id = scanIdOption(infos)

    fmt.Printf("Playing audio to: %s\n", infos[id].Name())

    deviceConfig = malgo.DefaultDeviceConfig(malgo.Playback)
    deviceConfig.Capture.Format = malgo.FormatS16
    deviceConfig.Capture.Channels = 1
    deviceConfig.Playback.Format = malgo.FormatS16
    deviceConfig.Playback.Channels = 1
    deviceConfig.SampleRate = 16000
    deviceConfig.Alsa.NoMMap = 1
    deviceConfig.PeriodSizeInMilliseconds = 40
    deviceConfig.Playback.DeviceID = infos[id].ID.Pointer()

    onSendFrames := func(pSample, nil []byte, framecount uint32) {
        samplesToRead := framecount * deviceConfig.Playback.Channels * sizeInBytes
        if samplesToRead > capturedSampleCount-playbackSampleCount {
            samplesToRead = capturedSampleCount - playbackSampleCount
        }

        copy(pSample, pCapturedSamples[playbackSampleCount:playbackSampleCount+samplesToRead])

        playbackSampleCount += samplesToRead

        if playbackSampleCount == uint32(len(pCapturedSamples)) {
            playbackSampleCount = 0
        }
    }

    fmt.Println("Playing...")
    playbackCallbacks := malgo.DeviceCallbacks{
        Data: onSendFrames,
    }

    device, err = malgo.InitDevice(context.Context, deviceConfig, playbackCallbacks)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    err = device.Start()
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    fmt.Println("Press Enter to quit...")
    fmt.Scanln()

    device.Uninit()

}

func scanIdOption(infos []malgo.DeviceInfo) int {
    fmt.Println("\r\nChoose an ID from the list above: ")
    var id int
    _, err := fmt.Scanf("%d", &id)
    if err != nil {
        log.Fatal(err)
    } else {
        fmt.Printf("\r\nChosen ID: [%d]\n", id)
    }

    if id >= len(infos) {
        fmt.Println("\r\nInvalid ID")
        panic("Invalid ID")
    }

    return id
}

If I comment deviceConfig.Capture.DeviceID = infos[id].ID.Pointer(), then I get audio captured from the default stream.

When this line is enabled, the callback is triggered and I received 640 bytes of zeroes. No data is received. Am I missing something here?

navinreddy23 commented 1 month ago

Looks like there are some devices listed here that are not capture devices.

Capture Devices
    ID: [0]: 616c73615f6f75747075742e7063692d303030305f30305f31662e332e616e616c6f672d73746572656f2e6d6f6e69746f72, Monitor of Built-in Audio Analog Stereo, [ok], formats: [{Format:4 Channels:2 SampleRate:48000 Flags:0}]
    ID: [1]: 616c73615f696e7075742e7063692d303030305f30305f31662e332e616e616c6f672d73746572656f, Built-in Audio Analog Stereo, [ok], formats: [{Format:4 Channels:2 SampleRate:48000 Flags:0}]
    ID: [2]: 616c73615f6f75747075742e7063692d303030305f30315f30302e312e68646d692d73746572656f2d6578747261312e6d6f6e69746f72, Monitor of GP107GL High Definition Audio Controller Digital Stereo (HDMI 2), [ok], formats: [{Format:4 Channels:2 SampleRate:48000 Flags:0}]
    ID: [3]: 626c75657a5f696e7075742e36305f46445f41365f30425f34375f33442e30, miPods, [ok], formats: [{Format:2 Channels:1 SampleRate:16000 Flags:0}]
    ID: [4]: 626c75657a5f6f75747075742e36305f46445f41365f30425f34375f33442e312e6d6f6e69746f72, Monitor of miPods, [ok], formats: [{Format:2 Channels:1 SampleRate:16000 Flags:0}]

Choose an ID from the list above: 

ID's 0, 2 and 4 are not Capture devices, yet they show up here.

    infos, err := context.Devices(malgo.Capture)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    fmt.Println("Capture Devices")
    for i, info := range infos {
        e := "ok"
        full, err := context.DeviceInfo(malgo.Capture, info.ID, malgo.Shared)
        if err != nil {
            e = err.Error()
        }
        fmt.Printf("    ID: [%d]: %v, %s, [%s], formats: %+v\n",
            i, info.ID, info.Name(), e, full.Formats)
    }

But it is not the case for Playback devices.

Playback Devices
    0: 616c73615f6f75747075742e7063692d303030305f30305f31662e332e616e616c6f672d73746572656f, Built-in Audio Analog Stereo, [ok], formats: [{Format:4 Channels:2 SampleRate:48000 Flags:0}]
    1: 616c73615f6f75747075742e7063692d303030305f30315f30302e312e68646d692d73746572656f2d657874726131, GP107GL High Definition Audio Controller Digital Stereo (HDMI 2), [ok], formats: [{Format:4 Channels:2 SampleRate:48000 Flags:0}]
    2: 626c75657a5f6f75747075742e36305f46445f41365f30425f34375f33442e31, miPods, [ok], formats: [{Format:2 Channels:1 SampleRate:16000 Flags:0}]

Choose an ID from the list above: 

In playback devices, Capture devices are not to be seen.

gen2brain commented 1 month ago

I don't see an issue, or perhaps I didn't understand you. What do you get from aplay -l and arecord -l, you should get the same list. Depending on the sound card you get a list of all possible devices, i.e. I get 5 playback devices but only one I can use, similar to capture devices, they are not playback but there are monitors and whatnot devices, pick the correct one based on channels, format, skip monitor devices, etc.

The point of enumeration is that you get a list of possible devices, and you pick and choose one, I don't think there is any guarantee here, anyway, these are bindings, and even if there is an issue it will not be solved here.

navinreddy23 commented 1 month ago

Hi @gen2brain,

I think the code is too much in the previous comment.

The Issue I faced initially:

If it helps anyone, I will attach captureToFile.txt the modified capture.go file to store in in a wav file.