Linux как записывать звук в буфер RAM и воспроизводить звук с настраиваемой задержкой

Мне нужно отправить звук с радио на вторичную систему, используя встроенную систему Linux.

Вторичной системе необходимо настроить канал связи, что занимает несколько секунд.

Поэтому, если я не хочу терять начало звука, мне нужен способ записать звук и воспроизвести его с настраиваемой задержкой (максимум несколько секунд).

Должна быть предусмотрена возможность запуска arecord для записи звука в файл в файловой системе tmpfs, а при входящем сообщении запускать aplay. Но в этом случае начало все равно потеряно, потому что сигнал на запись приходит слишком поздно.

Есть ли в Linux программа, которая непрерывно записывает звук в кольцевой буфер в ОЗУ и может воспроизводить звук с настраиваемой задержкой по запросу?

Если нет, то какая библиотека лучше всего для кодирования такой программы во встроенной системе? alsa или что-то еще?


person leszek.hanusz    schedule 04.09.2015    source источник
comment
В Linux каждая аудиотека в конечном итоге использует ALSA. Однако вы можете использовать любую другую библиотеку, если ее проще использовать.   -  person CL.    schedule 05.09.2015
comment
Этот вопрос не по теме, поскольку он требует инструмента или библиотеки?   -  person Thomas Weller    schedule 07.09.2015
comment
Alsa поддерживает плагины LADSPA, должен быть один с фиксированной задержкой.   -  person Phillip    schedule 07.09.2015
comment
Уместно ли написать сценарий, который будет работать с буфером и конвейером вместе, например in | buffer-me 5s | out? Такой сценарий было бы довольно тривиально написать, хотя, возможно, и наивно. Конечно, было бы эффективнее сделать это в существующем приемнике или источнике.   -  person thespinkus    schedule 13.09.2015


Ответы (2)


Вот простая программа на C, которая будет поддерживать кольцевой буфер между входом и выходом канала. Используйте как in | buffer_program | out. Проверка ошибок опущена. Надежность не гарантируется. Дает общее представление.

Тестовый скрипт (но на самом деле из-за его кольцевого буфера данные, в которых работает конвейер, должны быть такими, чтобы он согласованно принимал любой фрагмент в потоке. Или просто сделайте буфер больше, чем данные):

cat some.wav | ./circular_buffer 100000 | (sleep 1 && aplay)

round_buffer.c:

 /**
 * This program simply maintains a circular buffer of a given size indefinitely.
 */
#include <stdio.h>
#include <stddef.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h> /* C99 only */
#include <sys/select.h>
#include <errno.h>
#include <fcntl.h>

int c_read(int fd, char * buf, unsigned int size, unsigned int * head_in, unsigned int * tail_in);
int c_write(int fd, char * buf, unsigned int size, unsigned int * head_in, unsigned int * tail_in);
bool empty_buf(unsigned int head, unsigned int tail);
bool setblock(int fd, bool block);
#define FD_SET_SET(set, fd, max) FD_SET(fd, &set); max = ((fd > max) ? fd : max);
#define FD_SET_UNSET(set, fd, max) FD_CLR(fd, &set); max = ((fd == max) ? max - 1  : max);  //not ideal. Do while ISFDSET...

int main(int argc, char **argv)
{
  char * buf;
  unsigned int buf_size = 0;
  unsigned int buf_head = 0;
  unsigned int buf_tail = 0;

  // Check args.
  if(argc != 2) {
    fprintf(stderr, "Usage: %s <buffer size in bytes>\n", __FILE__);
    exit(EXIT_FAILURE);
  }
  sscanf(argv[1], "%d", &buf_size);
  buf_size = ( buf_size < 2 ) ? 2 : buf_size;

  // Note the usable buffer space is buf_size-1.
  fprintf(stderr, "Allocating %d\n", buf_size);
  buf = (char*)malloc(buf_size);

  bool done_reading = false;
  int maxfd = 0;
  fd_set r_set, w_set, r_tempset, w_tempset;
  setblock(STDIN_FILENO, false);
  setblock(STDOUT_FILENO, false);
  FD_ZERO(&r_set);
  FD_ZERO(&w_set);
  FD_ZERO(&r_tempset);
  FD_ZERO(&w_tempset);
  FD_SET_SET(r_tempset, STDIN_FILENO, maxfd);
  FD_SET_SET(w_tempset, STDOUT_FILENO, maxfd);
  r_set = r_tempset;
  while(true) {
    select((maxfd + 1), &r_set, &w_set, NULL, NULL);
    if(FD_ISSET(STDIN_FILENO, &r_set)) {
      int c = c_read(STDIN_FILENO, buf, buf_size, &buf_head, &buf_tail);
      if(c == -1) { // EOF, disable select on the input.
        fprintf(stderr, "No more bytes to read\n");
        done_reading = true;
        FD_ZERO(&r_set);
      }
    }
    if(!done_reading) {
      r_set = r_tempset;
    }
    if(FD_ISSET(STDOUT_FILENO, &w_set)) {
      c_write(STDOUT_FILENO, buf, buf_size, &buf_head, &buf_tail);
    }
    if(!empty_buf(buf_head, buf_tail)) { // Enable select on write whenever there is bytes.
      w_set = w_tempset;
    }
    else {
      FD_ZERO(&w_set);
      if(done_reading) { // Finish.
        fprintf(stderr, "No more bytes to write\n");
        break;
      }
    }
  }
  fflush(stderr);
  return 0;
}

bool empty_buf(unsigned int head, unsigned int tail) {
  return head == tail;
}

/**
 * Keep reading until we can read no more. Keep on pushing the tail forward as we overflow.
 * Expects fd to be non blocking.
 * @returns number of byte read, 0 on non stopping error, or -1 on error or EOF.
 */
int c_read(int fd, char * buf, unsigned int size, unsigned int * head_in, unsigned int * tail_in) {
  fprintf(stderr, "In c_read()\n");
  unsigned int head = *head_in;
  unsigned int tail = *tail_in;
  bool more_bytes = true;
  int n = 0;
  int c = 0;

  while(more_bytes) {
    bool in_front = tail > head;
    fprintf(stderr, "Read %d %d %d\n", size, head, tail);

    n = read(fd, buf+head, size - head);
    if(n == -1) {
      more_bytes = false;
      if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { // Not EOF but the read would block.
        c = 0;
      }
      else {
        c = -1;
      }
    }
    else if(n == 0) { // EOF. No more bytes possible.
      more_bytes = false;
      c = -1;
    }
    else if(n != (size - head)) { // if not full read adjust pointers and break.
      more_bytes = false;
      c += n;
      head = (head+n)%size;
      if(in_front && (head >= tail || head == 0)) {
        tail = (head+1)%size;
      }
    }
    else {
      c = 0;
      head = 0;
      tail = (tail == 0) ? 1 : tail;
    }
  }
  *head_in = head;
  *tail_in = tail;
  return c;
}

/**
 * Try flush the buffer to fd. fd should be non blocking.
 */
int c_write(int fd, char * buf, unsigned int size, unsigned int * head_in, unsigned int * tail_in) {
  fprintf(stderr, "In c_write()\n");
  unsigned int head = *head_in;
  unsigned int tail = *tail_in;
  int n = 0;
  fprintf(stderr, "Write %d %d %d\n", size, head, tail);

  if(tail < head) {
    n = write(fd, buf+tail, head-tail);
    tail += n;
  }
  else if(head < tail) {
    n = write(fd, buf+tail, size-tail);
    if(n == size-tail) {
      n = write(fd, buf, head);
      tail = n;
    }
  }
  *head_in = head;
  *tail_in = tail;
  return n;
}

bool setblock(int fd, bool block)
{
  int flags;
  flags = fcntl(fd, F_GETFL);
  if (block)
      flags &= ~O_NONBLOCK;
  else
      flags |= O_NONBLOCK;
  fcntl(fd, F_SETFL, flags);
  return true;
}
person thespinkus    schedule 13.09.2015

Если все, что вам нужно, это иметь буфер для вывода звука до тех пор, пока он не будет готов к использованию, вариант этого должен работать:

Начать запись:

mkfifo /tmp/f
stdbuf -o256M arecord -i | cat > /tmp/f

Начните играть, когда ваше устройство вывода будет готово:

aplay /tmp/f

Настройте размер выходного буфера в соответствии с вашими потребностями.

ИЗМЕНИТЬ (учитывая, что игра может начаться в любое время):

Если вам нужно непрерывно записывать и начинать воспроизведение в любое время, вы можете разделить вывод на более мелкие файлы с помощью команды split и удалить старые файлы во вспомогательном процессе.

Что-то типа:

# Garbage collector
( while sleep 1 ; do rm $(ls *.blb 2>/dev/null | sort | head -n-3 ) > /dev/null 2>&1 ; done ) &
# Actual recording
arecord -i | split -a 10 -u -b 24576 --additional-suffix '.blb'

И играть:

{ while true ; do for f in $(find . -name '*.blb' -size 24576c | sort) ; do cat $f ; rm $f ; done ; done } | aplay

Это решение довольно грязное, но может работать (желательно на tmpfs, как вы уже упомянули) ...

person vlp    schedule 09.09.2015
comment
Я хотел бы записывать все время с круговым буфером (может быть только 240 КБ для 60 с 8000 Гц 8-битного звука), а затем, когда он должен воспроизводиться, он будет использовать dd для воспроизведения из правильной позиции в буфере. В вашем случае stdbuf заблокируется, когда буфер заполнится. - person leszek.hanusz; 11.09.2015
comment
Тогда я неправильно понял ваш вопрос. Я отредактирую другую идею (которая не является идеальным решением, но может быть рабочим решением) ... - person vlp; 11.09.2015