LINUX下实时录放音(OSS)—解决时延问题

#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <stdio.h>
#include <linux/soundcard.h>
#include <termios.h>



#define RATE 16000 //采样频率
#define SIZE 16 //量化位数
#define CHANNELS 1 //声道数目
#define RSIZE 80 //buf的大小,



int main(void)
{
    int fd_dev_r;
    int fd_dev_w;
    int fd_f;
    int arg;
    int status;
       char choice;
       int i;
    unsigned char buf[RSIZE]; //每次循环取得RSIZE大小的容量,放入buf,然后写入文件;


    //打开声卡设备,只读方式;并对声卡进行设置
    fd_dev_r= open("/dev/dsp", O_RDONLY,0777);
    if (fd_dev_r < 0)
    {
       perror("Cannot open /dev/dsp device");
       return 1;
    }
    arg = SIZE;
       status = ioctl(fd_dev_r, SOUND_PCM_WRITE_BITS, &arg);//设置量化位数
    if (status == -1)
    {
           perror("Cannot set SOUND_PCM_WRITE_BITS ");
           return 1;
    }
    arg = CHANNELS;
    status = ioctl(fd_dev_r, SOUND_PCM_WRITE_CHANNELS, &arg);//设置声道数
    if (status == -1)
       {
           perror("Cannot set SOUND_PCM_WRITE_CHANNELS");
           return 1;
       }
    arg = RATE;
    status = ioctl(fd_dev_r, SOUND_PCM_WRITE_RATE, &arg);//设置采样率
    if (status == -1)
    {
           perror("Cannot set SOUND_PCM_WRITE_WRITE");
           return 1;
    }
    //打开声卡设备,只写方式;并对声卡进行设置
    fd_dev_w = open("/dev/dsp", O_WRONLY,0777);
    if (fd_dev_w < 0)
    {
           perror("Cannot open /dev/dsp device");
           return 1;
    }
    arg = SIZE;
    status = ioctl(fd_dev_w, SOUND_PCM_WRITE_BITS, &arg);//设置量化位数
       if (status == -1)
    {
          perror("Cannot set SOUND_PCM_WRITE_BITS ");
        return 1;
    }
    arg = CHANNELS;
    status = ioctl(fd_dev_w, SOUND_PCM_WRITE_CHANNELS, &arg);//设置声道数
    if (status == -1)
    {
        perror("Cannot set SOUND_PCM_WRITE_CHANNELS");
           return 1;
       }
    arg = RATE;
    status = ioctl(fd_dev_w, SOUND_PCM_WRITE_RATE, &arg);//设置采样率
       if (status == -1)
    {
           perror("Cannot set SOUND_PCM_WRITE_WRITE");
           return 1;
    }



     for(;;)
       {
       status = read(fd_dev_r, buf, sizeof(buf));
       if (status != sizeof(buf))
       {
        perror("read wrong number of bytes");
        }

        status = write(fd_dev_w, buf, sizeof(buf));//送声卡播放
          if (status != sizeof(buf))
        perror("wrote2 wrong number of bytes");

    }
    close(fd_dev_r);//关闭只读方式的声卡
    close(fd_dev_w);
    return 0;

}

如果没有强制设置buffer大小,系统默认的缓冲buffer的大小是2*4096=8192字节,其中2表示的是fragement的数量,4096是每个fragementsize的大小。系统读取音频数据之后,放音的时候,先放入缓冲buffer中,当缓冲buffer填满时,系统才会产生中断,播放音频。所以这里会存在一个延迟。像系统默认的buffer大小,如果录制的是16khz 16bit的音频的话,延迟大小是就是2 * 4096 / (16000 * 2) = 256ms。如果程序要求实时性,就必须得修改buffer的大小。

在OSS的ioctl接口中,SNDCTL_DSP_SETFRAGMENT就是用来设置驱动程序内部缓冲区大小。具体的用法如下:(特别注意的是这个设置要放在所有设置之前,不然会出问题)

int param;
param = ( 0x0004 << 16) + 0x000a;
if (ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &param) == -1) {  
                ...error handling...
}

参数param由两部分组成:低16位为fragment的大小,此处0x000a表示fragment大小为2^0xa,即1024字节;高16位为fragment的数量,此处为0x0004,即4个fragement。设置好fragment参数后,通过ioctl的SNDCTL_DSP_SETFRAGMENT命令调整驱动程序中的缓冲区。

如果你要查看系统的buffer的大小的话,也可以通过以下程序获得

int frag_size;
ioctl(audio_fd, SNDCTL_DSP_GETBLKSIZE, &frag_size) 

也可以通过以下程序获得更详细的信息:

audio_buf_info info;
if (ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info) == -1)
    {
        printf("SNDCTL_DSP_GETOSPACE error\n");
    }

printf("bytes:%d fragments:%d fragsize:%d fragstotal:%d\n",info.bytes,info.fragments,info.fragsize,info.fragstotal);

解决时延的完成程序

#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/soundcard.h>
#include <sys/time.h>
#include <time.h>
#define DEVICE_NAME "/dev/dsp"

#include <stdio.h>
#include <malloc.h>


#define frame_size 80

int audio_fd;
int recoder_fid;


audio_buf_info info;

int main(int argc, char *argv[])
{
    short int i;
    short int  tempVar;

    int status;
    int sampleRate = 16000;  //16khz
    int format = 16;
    int channels = 1;

    int frag_size= 0;

    FILE *fp_in;
    short int *SigBuf;


    int FrameCounter=0;


    char *read_mic_file = "read_from_mic.pcm";


    printf("时延 (128*2)/(16000*2)= 8ms \n");


    fp_in=fopen(read_mic_file,"wb");




    recoder_fid = open(DEVICE_NAME, O_RDONLY,0777);
    if (recoder_fid < 0)
    {
        perror("Cannot open /dev/dsp device");
        return 1;
    }





    status = ioctl(recoder_fid, SOUND_PCM_WRITE_BITS, &format);//设置量化位数
    if(status == -1)
    {
        perror("Cannot set SOUND_PCM_WRITE_BITS ");
        return 1;
    }

    status = ioctl(recoder_fid, SOUND_PCM_WRITE_CHANNELS, &channels);//设置声道数
    if (status == -1)
    {
        perror("Cannot set SOUND_PCM_WRITE_CHANNELS");
        return 1;
    }

    status = ioctl(recoder_fid, SOUND_PCM_WRITE_RATE, &sampleRate);//设置采样率
    if (status == -1)
    {
        perror("Cannot set SOUND_PCM_WRITE_WRITE");
        return 1;
    }


    if ((audio_fd = open(DEVICE_NAME, O_WRONLY,0777)) == -1)
    {
       printf("open error\n");
       return -1;
    }





    if (ioctl(audio_fd, SOUND_PCM_WRITE_BITS, &format) == -1)
    {
       /* fatal error */
       printf("SNDCTL_DSP_SETFMT error\n");
       return -1;
    }

    int param;
    param = ( 0x0002 << 16) + 0x0006; //参数param由两部分组成:低16位为fragment的大小,此处0x0007表示fragment大小为2^7,即128字节;
                                     //高16位为fragment的数量,此处为0x0002,即2个fragement。
    if (ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &param) == -1) {
        printf("SNDCTL_DSP_SETFRAGMENT error\n");
    }


    if (ioctl(audio_fd, SOUND_PCM_WRITE_CHANNELS, &channels) == -1)
    {
       /* Fatal error */
       printf("SOUND_PCM_WRITE_CHANNELS error");
       return -1;
    }

    if (ioctl(audio_fd, SOUND_PCM_WRITE_RATE, &sampleRate)==-1)
    {
       /* Fatal error */
       printf("SOUND_PCM_WRITE_RATE error\n");
       return -1;
    }


    int version = 0;
    if (ioctl(audio_fd, OSS_GETVERSION, &version) == -1) {
        printf("Failed to get OSS version\n");
    }

    printf("OSS version is:%x \n",version);
    ioctl(audio_fd, SNDCTL_DSP_GETBLKSIZE, &frag_size);
    printf("fragment size is:%d \n",frag_size);

    if (ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info) == -1)
    {
        printf("SNDCTL_DSP_GETOSPACE error\n");
    }

    printf("bytes:%d fragments:%d fragsize:%d fragstotal:%d\n",info.bytes,info.fragments,info.fragsize,info.fragstotal);

    printf("the wav sampleRate is %d\n",sampleRate);
    printf("format: %d \n",format);


    SigBuf = calloc(1, sizeof(short int) * frame_size);




    while (read(recoder_fid, SigBuf, sizeof(short int)*frag_size))
    {
      fwrite(SigBuf,sizeof(short int),frag_size,fp_in);
      FrameCounter++;

      write(audio_fd, SigBuf, sizeof(short int)*frag_size);

    }

    printf("Frame number:%d \n",FrameCounter);

    close(recoder_fid);
    close(audio_fd);

    fclose(fp_in);
    free(SigBuf);
    return 0;

}

程序运行图如下:

但是从官方的文档来看,官方并不推荐这样做,http://manuals.opensound.com/developer/audio_timing.html 因为系统默认的buffer是根据你的采样率等设置自动计算的。强制设置buffer未必会达到你想要的结果,就像刚才说的,buffer的设置不能放在后面,之前我是放在后面,程序一开始运行不会出错,但是等一两分之后,程序出现了非常大的延迟,大概有两秒左右。由上面的程序运行图可以知道,OSS版本是3.08。OSS现在已经升级到4.0,所以OSS4.0也对这个时延进行了改进。http://manuals.opensound.com/developer/SNDCTL_DSP_POLICY.html 可以通过以下程序设置:

int policy=The policy value;
ioctl(fd, SNDCTL_DSP_POLICY, &policy);

policy的值来设定时延的大小。Values below 5 will give lower and lower latencies with slightly increased CPU usage. Values above 5 will give lower CPU usage with increased latencies.

你可能感兴趣的:(linux,oss,时延,录放音)