malgo icon indicating copy to clipboard operation
malgo copied to clipboard

Q: how to encode input audio bytes to PCM properly

Open husain-zaidi opened this issue 3 years ago • 1 comments

I am a bit of a novice to audio encoding. I am attempting to capture desktop audio using WASAPI loopback Code here: https://gist.github.com/husainhz7/c8d4fac481a3b860243566835afaeca0

I want to feed this audio to libopus to encode an opus stream. libopus requires PCM encoded floats/ints. What is the best way to do that here. I see that miniaudio provides 'ma_encoder_write_pcm_frames' which I don't think is supported by malgo.

husain-zaidi avatar May 30 '22 13:05 husain-zaidi

Hi @husainhz7,

I was able to throw together the following to capture raw PCM and save it to a file. I used Audacity to import the raw stream to confirm it did correctly get captured.

package main

import (
	"fmt"
	"log"
	"path/filepath"

	"os"
	"time"

	"github.com/gen2brain/malgo"
)

// Create function to sample music and return basename.
func sample() string {

	ctx, err := malgo.InitContext(nil, malgo.ContextConfig{}, func(message string) {
		fmt.Printf("LOG <%v>\n", message)
	})
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	defer func() {
		_ = ctx.Uninit()
		ctx.Free()
	}()

	deviceConfig := malgo.DefaultDeviceConfig(malgo.Duplex)
	deviceConfig.Capture.Format = malgo.FormatS32
	deviceConfig.Capture.Channels = 1
	deviceConfig.SampleRate = 8000

	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
		newCapturedSampleCount := capturedSampleCount + sampleCount
		pCapturedSamples = append(pCapturedSamples, pSample...)
		capturedSampleCount = newCapturedSampleCount
	}

	captureCallbacks := malgo.DeviceCallbacks{
		Data: onRecvFrames,
	}

	device, err := malgo.InitDevice(ctx.Context, deviceConfig, captureCallbacks)
	if err != nil {
		log.Printf("Failed to init device: %v", err)
		os.Exit(1)
	}

	err = device.Start()
	if err != nil {
		log.Printf("Failed to start device: %v", err)
		os.Exit(1)
	}

	// Count to 10 seconds
	for i := 11; i > 1; i-- {
		fmt.Printf("Sampling audio for %d seconds                      \r", i)
		time.Sleep(time.Second)
	}

	basename := filepath.Join("tmp", time.Now().Format("20060102150405"))
	rawFile := fmt.Sprintf("%s.pcm", basename)

	// Save pCapturedSamples to file
	f, err := os.Create(rawFile)
	if err != nil {
		log.Printf("Error creating file: %v", err)
	}
	defer f.Close()

	_, err = f.Write(pCapturedSamples)
	if err != nil {
		log.Printf("Error saving PCM data: %v", err)
	}

	device.Uninit()

	return basename + ".pcm"
}

The pCapturedSamples holds your raw frames. You should be able to pass this data directly into your ma_encorder_write_pcm_frames() method as-is.

maietta avatar Jun 10 '22 21:06 maietta