diff options
| author | fschildt <florian.schildt@protonmail.com> | 2025-08-22 15:23:11 +0200 | 
|---|---|---|
| committer | fschildt <florian.schildt@protonmail.com> | 2025-10-15 11:33:23 +0200 | 
| commit | 04e4627e6c11254ee6f49edf5feb1b8d711da41a (patch) | |
| tree | e28f2e62d3e1b83f9686cdeb102e3f47379e6793 /src/os/linux/linux_sound.c | |
Diffstat (limited to 'src/os/linux/linux_sound.c')
| -rw-r--r-- | src/os/linux/linux_sound.c | 269 | 
1 files changed, 269 insertions, 0 deletions
diff --git a/src/os/linux/linux_sound.c b/src/os/linux/linux_sound.c new file mode 100644 index 0000000..2669d40 --- /dev/null +++ b/src/os/linux/linux_sound.c @@ -0,0 +1,269 @@ +// Todo: Use a high-priority thread for playing the sound buffer. +// Todo: refactor SND_CALL and other snd_... calls to deal with errors/state better. +// Note: Currently, the snd-api's buffer is large enough to write everything at once + +#ifndef _POXIS_C_SOURCE +#define _POSIX_C_SOURCE 200809L // enable POSIX.1-2017 +#endif + +#include <os/os.h> +#include <basic/basic.h> +#include <basic/arena.h> +#include <alsa/asoundlib.h> + + +struct OSSoundPlayer { +    snd_pcm_t *pcm; +    u32 frames_per_period; +    u32 samples_per_second; +    i32 channel_count; +    i32 max_sample_count; +    OSSoundBuffer sound_buffer; +}; + + +#define SND_CALL(snd_call) \ +{ \ +    int error = snd_call; \ +    if (error < 0) { \ +        const char *error_str = snd_strerror(error); \ +        printf(#snd_call" err: %s\n", error_str); \ +        snd_pcm_close(pcm); \ +        return false; \ +    } \ +} + +internal_fn void +os_list_sound_devices(void) +{ +   int status; +   int card = -1; +   char* longname  = NULL; +   char* shortname = NULL; + +   if ((status = snd_card_next(&card)) < 0) { +      printf("cannot determine card number: %s", snd_strerror(status)); +      return; +   } +   if (card < 0) { +      printf("no sound cards found"); +      return; +   } +   while (card >= 0) { +      printf("Card %d:", card); +      if ((status = snd_card_get_name(card, &shortname)) < 0) { +         printf("cannot determine card shortname: %s", snd_strerror(status)); +         break; +      } +      if ((status = snd_card_get_longname(card, &longname)) < 0) { +         printf("cannot determine card longname: %s", snd_strerror(status)); +         break; +      } +      printf("\tLONG NAME:  %s\n", longname); +      printf("\tSHORT NAME: %s\n", shortname); + + +      char cardname[128]; +      //sprintf(cardname, "default"); +      sprintf(cardname, "hw:%d", card); +      printf("cardname = %s\n", cardname); + +      snd_ctl_t *ctl; +      int mode = SND_PCM_ASYNC; // SND_PCM_NONBLOCK or SND_PCM_ASYNC +      if (snd_ctl_open(&ctl, cardname, mode) >= 0) { +          printf("cannot snd_stl_open\n"); +          int device = -1; +          if (snd_ctl_pcm_next_device(ctl, &device) < 0) { +              printf("no devices found\n"); +              device = -1; +          } + +          if (device == -1) { +              printf("no device\n"); +          } +          while (device >= 0) { +              char devicename[256]; +              sprintf(devicename, "%s,%d", cardname, device); +              printf("devicename = %s\n", devicename); + +              snd_pcm_t *pcm_handle; +              snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK; // SND_PCM_STREAM_PLAYBACK or SND_PCM_STREAM_CAPTURE +              if (snd_pcm_open(&pcm_handle, devicename, stream, mode) >= 0) { +                  printf("snd_pcm_open success\n"); +                  snd_pcm_close(pcm_handle); +              } +              else { +                  printf("snd_pcm_open failed\n"); +              } + +              if (snd_ctl_pcm_next_device(ctl, &device) < 0) { +                  printf("snd_ctl_pcm_next_device error\n"); +                  break; +              } +          } + +          snd_ctl_close(ctl); +      } +      else { +          printf("snd_ctl_open failed\n"); +      } + + +      if ((status = snd_card_next(&card)) < 0) { +         printf("cannot determine card number: %s", snd_strerror(status)); +         break; +      } +   }  +} + +internal_fn b32 +os_sound_player_recover(snd_pcm_t *pcm, i64 err) +{ +    if (err == -EPIPE) { +        err = snd_pcm_prepare(pcm); +        if (err < 0) { +            printf("Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err)); +            return false; +        } +        return true; +    } +    else if (err == -ESTRPIPE) { +        while ((err = snd_pcm_resume(pcm)) == -EAGAIN) { +            struct timespec ts = {0, 100}; +            nanosleep(&ts, 0); +        } +        if (err < 0) { +            err = snd_pcm_prepare(pcm); +            if (err < 0) { +                printf("Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err)); +                return false; +            } +            return true; +        } +        return true; +    } +    printf("Can't recover: %s\n", snd_strerror(err)); +    return false; +} + + +void +os_sound_player_play(OSSoundPlayer *player) +{ +    OSSoundBuffer *buffer = &player->sound_buffer; + +    i16 *samples = player->sound_buffer.samples; +    u32 frames_to_write = buffer->sample_count / player->channel_count; +    while (frames_to_write) { +        snd_pcm_sframes_t frames_written = snd_pcm_writei(player->pcm, samples, frames_to_write); +        if (frames_written < 0) { +            if (!os_sound_player_recover(player->pcm, frames_written)) { +                return; +            } +            continue; +        } +        samples += frames_written * player->channel_count; +        frames_to_write -= frames_written; +    } + +    int state = snd_pcm_state(player->pcm); +    if (state != SND_PCM_STATE_RUNNING) { +        snd_pcm_t *pcm = player->pcm; +        snd_pcm_start(pcm); +    } +} + +void +os_sound_player_close(OSSoundPlayer *player) +{ +    snd_pcm_close(player->pcm); +    free(player->sound_buffer.samples); +} + +OSSoundBuffer * +platform_sound_player_get_buffer(OSSoundPlayer *player) +{ +    return &player->sound_buffer; +} + +OSSoundBuffer * +os_sound_player_get_buffer(OSSoundPlayer *player) +{ +    i64 frames_avail = snd_pcm_avail(player->pcm); +    if (frames_avail < 0) { +        if (os_sound_player_recover(player->pcm, frames_avail)) { +            frames_avail = 0; +        } +        frames_avail = snd_pcm_avail(player->pcm); +        if (frames_avail < 0) { +            frames_avail = 0; +        } +    } + +    OSSoundBuffer *buffer = &player->sound_buffer; +    buffer->sample_count = frames_avail*2; +    memset(buffer->samples, 0, buffer->sample_count * sizeof(i16)); +    return buffer; +} + +OSSoundPlayer * +os_sound_player_create(Arena *arena, i32 samples_per_second) +{ +    // open pcm +    snd_pcm_t *pcm = 0; +    const char *device_name = "default"; +    int stream = SND_PCM_STREAM_PLAYBACK; +    int mode = SND_PCM_ASYNC; + + +    SND_CALL(snd_pcm_open(&pcm, device_name, stream, mode)); + + +    // set hw params + +    snd_pcm_hw_params_t *params; +    snd_pcm_hw_params_alloca(¶ms); + +    SND_CALL(snd_pcm_hw_params_any(pcm, params)); // get full configuration space + +    int access = SND_PCM_ACCESS_RW_INTERLEAVED; +    SND_CALL(snd_pcm_hw_params_set_access(pcm, params, access)); + +    int format = SND_PCM_FORMAT_S16_LE; +    SND_CALL(snd_pcm_hw_params_set_format(pcm, params, format)); + +    u32 channels = 2; +    SND_CALL(snd_pcm_hw_params_set_channels(pcm, params, channels)); + +    u32 sample_rate = 44100; +    SND_CALL(snd_pcm_hw_params_set_rate_near(pcm, params, &sample_rate, 0)); + +    u32 period_count = 3; +    u32 sample_count = period_count * (sample_rate / 30); +    u32 frame_count  = sample_count / channels; +    SND_CALL(snd_pcm_hw_params_set_buffer_size(pcm, params, frame_count)); + +    u32 frames_per_period = frame_count / period_count; +    SND_CALL(snd_pcm_hw_params_set_period_size(pcm, params, frames_per_period, 0)); + +    SND_CALL(snd_pcm_hw_params(pcm, params)); + + +    // create player +    OSSoundPlayer *player = arena_push(arena, sizeof(OSSoundPlayer)); +    memset(player, 0, sizeof(*player)); +    player->pcm = pcm; +    player->frames_per_period = frames_per_period; +    player->samples_per_second = sample_rate; +    player->channel_count = channels; + +    // Todo: Redesign this player & buffer interaction.  +    // create buffer +    OSSoundBuffer *sound_buffer = &player->sound_buffer; +    sound_buffer->sample_count = 0; +    sound_buffer->max_sample_count = sample_count; +    sound_buffer->samples = arena_push(arena, sample_count * sizeof(i16)); + +    return player; +} +  | 
