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

Posted

技术标签:

【中文标题】Linux如何在RAM缓冲区中录制声音并以自定义延迟播放音频【英文标题】:Linux how to record sound in RAM buffer and playback audio with custom delay 【发布时间】:2015-11-30 17:13:02 【问题描述】:

我需要使用嵌入式 linux 系统将音频从收音机发送到辅助系统。

辅助系统需要建立一个通信通道,需要几秒钟。

因此,如果我不想丢失音频的开头,我需要一种方法来录制声音并以自定义延迟(最多几秒钟)进行播放。

应该可以启动arecord 以将音频录制到 tmpfs 文件系统的文件中,并且当有通信传入时,启动 aplay。 但在这种情况下,开始仍然丢失,因为记录的信号来得太晚了。

Linux 上是否有一个程序可以在 RAM 的环形缓冲区中连续录制声音并能够按需自定义延迟播放?

如果不是,在嵌入式系统上编写此类程序的最佳库是什么? alsa 还是别的什么?

【问题讨论】:

在 Linux 上,每个音频库最终都使用 ALSA。但是,如果它更易于使用,您可以使用任何其他库。 这个问题是否离题,因为它要求工具或库? Alsa 支持 LADSPA 插件,应该有一个具有固定延迟的插件。 是否适合编写一个脚本来像in | buffer-me 5s | out 这样一起完成缓冲区和管道工作?如果可能以一种幼稚的方式编写这样的脚本,那将是相当微不足道的。当然,在现有的接收器或源中执行此操作会更有效。 【参考方案1】:

如果您只需要一个缓冲区来保持声音输出直到它可以被使用,那么这个变体应该可以工作:

开始录制:

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 上)...

【讨论】:

我想一直用循环缓冲区记录(对于 60 秒的 8000Hz 8bit 音频只能是 240K),然后当它应该播放时,它会使用 dd 从正确的位置播放缓冲区。在您的情况下,当缓冲区已满时,stdbuf 将阻塞。 那我误解了你的问题。我将用另一个想法进行编辑(这不是一个完美的解决方案,但可能是一个可行的解决方案)...【参考方案2】:

这是一个简单的 C 程序,它将在管道输入和输出之间维护一个循环缓冲区。像in | buffer_program | out 一样使用。省略了错误检查。不保证稳健性。给出总体思路。

测试脚本(但实际上,由于它的循环缓冲区,您的管道中的数据需要使其连贯地获取流中的任何块。或者只是使缓冲区大于数据):

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

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;

【讨论】:

以上是关于Linux如何在RAM缓冲区中录制声音并以自定义延迟播放音频的主要内容,如果未能解决你的问题,请参考以下文章

使用 OpenAL(C++) 录制声音。缓冲区大小

使用waveIn和waveOut在windows中进行音频录制和实时播放

如何检测录制的声音样本的音高

录制的声音比原始声音慢

C++ 在 Linux 中录制 PCM 流

使用 NAudio 从麦克风录制声音。为啥不能正确地从列表中记录整个缓冲区?