Linux 如何在 RAM 缓冲区中录制声音并使用自定义延迟播放音频
Linux how to record sound in RAM buffer and playback audio with custom delay
我需要使用嵌入式 linux 系统将音频从收音机发送到辅助系统。
副系统需要建立通信通道,需要几秒时间
因此,如果我不想丢失音频的开头,我需要一种方法来录制声音并以自定义延迟(最多几秒)播放它。
应该可以开始 arecord
将音频记录在 tmpfs 文件系统的文件中,并且当有通信传入时,可以开始 aplay
。
但在这种情况下,开头仍然丢失,因为记录信号来得太晚了。
Linux 上是否有一个程序可以在 RAM 的环形缓冲区中连续录制声音并能够按需自定义延迟播放?
如果不是,在嵌入式系统上编写此类程序的最佳库是什么? alsa 或其他东西?
如果您只需要一个缓冲区来保持声音输出,直到它准备好被使用,那么这个变体应该可以工作:
开始录制:
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 上)...
这是一个简单的 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 系统将音频从收音机发送到辅助系统。
副系统需要建立通信通道,需要几秒时间
因此,如果我不想丢失音频的开头,我需要一种方法来录制声音并以自定义延迟(最多几秒)播放它。
应该可以开始 arecord
将音频记录在 tmpfs 文件系统的文件中,并且当有通信传入时,可以开始 aplay
。
但在这种情况下,开头仍然丢失,因为记录信号来得太晚了。
Linux 上是否有一个程序可以在 RAM 的环形缓冲区中连续录制声音并能够按需自定义延迟播放?
如果不是,在嵌入式系统上编写此类程序的最佳库是什么? alsa 或其他东西?
如果您只需要一个缓冲区来保持声音输出,直到它准备好被使用,那么这个变体应该可以工作:
开始录制:
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 上)...
这是一个简单的 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;
}