简体   繁体   English

Linux如何在RAM缓冲区中录制声音并以自定义延迟播放音频

[英]Linux how to record sound in RAM buffer and playback audio with custom delay

I need to send audio from a radio to a secondary system using an embedded linux system. 我需要使用嵌入式Linux系统将音频从收音机发送到辅助系统。

The secondary system needs to set up a communication channel which takes a few seconds. 辅助系统需要建立一个需要几秒钟的通信信道。

So if I don't want to lose the beginning of the audio, I need a way to record the sound and play it back with a custom delay (a few seconds maximum). 因此,如果我不想丢失音频的开头,我需要一种方法来录制声音并以自定义延迟播放(最多几秒钟)。

It should be possible to start arecord to record the audio in a file in a tmpfs filesystem, and, when a communication is incoming, start aplay . 应该可以启动arecord以将音频记录在tmpfs文件系统中的文件中,并且当传入通信时,启动aplay But in this case the beginning is still lost because the signal to record is coming too late. 但在这种情况下,开始仍然失败,因为要记录的信号来得太晚了。

Is there a program on Linux which is recording sound continuously in a ring buffer in RAM and able to playback with a custom delay on demand ? Linux上是否有一个程序可以在RAM中的环形缓冲区中连续录制声音,并且能够根据需要自定义延迟播放?

If not, what is the best library to code such a program on an embedded system ? 如果没有,在嵌入式系统上编写这样一个程序的最佳库是什么? alsa or something else ? alsa还是其他什么?

Here is a simple C program that will maintain a circular buffer between pipe in and out. 这是一个简单的C程序,它将在管道输入和输出之间保持循环缓冲区。 Use like in | buffer_program | out in | buffer_program | out使用 in | buffer_program | out in | buffer_program | out . in | buffer_program | out Error checking omitted. 错误检查省略。 Robustness not guaranteed. 坚固性不保证。 Gives the general idea. 给出了一般的想法。

Test script (but actually since its circular buffer the data your piping in needs to be such that its coherent taking just any chunk in the stream. Or just make the buffer bigger than the data): 测试脚本(但实际上,因为它的循环缓冲区需要管道所需的数据,以便它只需要流中的任何块。或者只是使缓冲区大于数据):

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

circular_buffer.c: circular_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;
}

If all you need is to have a buffer to keep sound output until it is ready to be consumed a variant of this should work: 如果您只需要一个缓冲区来保持声音输出,直到它准备好被消耗,那么这个变体应该有效:

Start recording: 开始录制:

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

Start playing, when yours output device is ready: 当您的输出设备准备就绪时开始播放:

aplay /tmp/f

Tweak the output buffer size to fit your needs. 调整输出缓冲区大小以满足您的需求。

EDIT (given that playing can start anytime): 编辑(鉴于可以随时开始播放):

If you need to continuously record and start playing anytime you could split the output into smaller files using the split command and delete the old files in a helper process. 如果您需要连续记录并开始播放任何时候你可能分裂成输出使用较小的文件split在助手进程的命令并删除旧文件。

Something like: 就像是:

# 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'

And to play: 并发挥:

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

This solution is quite dirty, but might work (preferably on tmpfs as you have already mentioned)... 这个解决方案很脏,但可能有效(最好是你已经提到过的tmpfs)...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM