ffmpeg学习九:音频编码前奏-ubuntu下录音和播放

上一篇博客,我们把一个Yuv编码格式的视频文件编码为H264格式。那么接下来,自然要学习下音频编码了。在学习音频编码之前,我们先看看ubuntu下如何采集声音和播放声音。

录音

录制5秒钟的一段音频。
audio_recorder.c:

/*created by Jinwei Liu*/
#define ALSA_PCM_NEW_HW_PARAMS_API  

#include   

int main(int argc,char **argv) {  
  long loops;  
  int rc;  
  int size;  
  snd_pcm_t *handle;  
  snd_pcm_hw_params_t *params;  
  unsigned int val;  
  int dir;  
  snd_pcm_uframes_t frames;  
  char *buffer;  
  FILE * fd_out;
  if(argc!=2){
    printf("usage:./a.out outfile\n");
  }
  /* Open PCM device for recording (capture). */  
  rc = snd_pcm_open(&handle, "default",  
                    SND_PCM_STREAM_CAPTURE, 0);  
  if (rc < 0) {  
    fprintf(stderr,  
            "unable to open pcm device: %s\n",  
            snd_strerror(rc));  
    exit(1);  
  }  

  /* Allocate a hardware parameters object. */  
  snd_pcm_hw_params_alloca(¶ms);  

  /* Fill it in with default values. */  
  snd_pcm_hw_params_any(handle, params);  

  /* Set the desired hardware parameters. */  

  /* Interleaved mode */  
  snd_pcm_hw_params_set_access(handle, params,  
                      SND_PCM_ACCESS_RW_INTERLEAVED);  

  /* Signed 16-bit little-endian format */  
  snd_pcm_hw_params_set_format(handle, params,  
                              SND_PCM_FORMAT_S16_LE);  

  /* Two channels (stereo) */  
  snd_pcm_hw_params_set_channels(handle, params, 2);  

  /* 11025 bits/second sampling rate (CD quality) */  
  val = 11025;  
  snd_pcm_hw_params_set_rate_near(handle, params,  
                                  &val, &dir);  

  /* Set period size to 32 frames. */  
  //frames = 32;  
 // snd_pcm_hw_params_set_period_size_near(handle,  
 //                             params, &frames, &dir);  

  /* Write the parameters to the driver */  
  rc = snd_pcm_hw_params(handle, params);  
  if (rc < 0) {  
    fprintf(stderr,  
            "unable to set hw parameters: %s\n",  
            snd_strerror(rc));  
    exit(1);  
  }  

  /* Use a buffer large enough to hold one period */  
  snd_pcm_hw_params_get_period_size(params,  
                                      &frames, &dir);  
  size = frames * 4; /* 2 bytes/sample, 2 channels */  
  buffer = (char *) malloc(size);  

  /* We want to loop for 5 seconds */  
  snd_pcm_hw_params_get_period_time(params,  
                                         &val, &dir);  
  loops = 5000000 / val;  
  fd_out = fopen(argv[1],"w+");
  while (loops > 0) {  
    loops--;  
    rc = snd_pcm_readi(handle, buffer, frames);  
    if (rc == -EPIPE) {  
      /* EPIPE means overrun */  
      fprintf(stderr, "overrun occurred\n");  
      snd_pcm_prepare(handle);  
    } else if (rc < 0) {  
      fprintf(stderr,  
              "error from read: %s\n",  
              snd_strerror(rc));  
    } else if (rc != (int)frames) {  
      fprintf(stderr, "short read, read %d frames\n", rc);  
    }  
    rc = fwrite(buffer,1, size,fd_out);  
    if (rc != size)  
      fprintf(stderr,  
              "short write: wrote %d bytes\n", rc);  
  }  
  fclose(fd_out);
  snd_pcm_drain(handle);  
  snd_pcm_close(handle);  
  free(buffer);  

  return 0;  
}  

函数中已经有相关的注释了。录音可以认为有三部分组成:
第一:参数设置阶段。这一阶段需要设置波特率,帧率,通道数等。
第二:采集阶段。主要就是调用snd_pcm_readi都声音数据。
第三:保存阶段。把都出来的数据写入文件即可。
编译:
gcc -o recorder audio_recorder.c -lasound
运行:
./recorder audio.pcm
执行完成后生成audio.pcm文件。

播放原始音频数据

直接播放原始音频文件(注意,录制的参数和播放的参数要一致)
audio_palyer.c

/*created by Jinwei Liu*/

/* Use the newer ALSA API */  
#define ALSA_PCM_NEW_HW_PARAMS_API  

#include   

int main(int argc,char **argv) {  
  long loops;  
  int rc;  
  int size;  
  snd_pcm_t *handle;  
  snd_pcm_hw_params_t *params;  
  unsigned int val;  
  int dir;  
  snd_pcm_uframes_t frames;  
  char *buffer;  
  int fd;

  /* Open PCM device for playback. */  
  rc = snd_pcm_open(&handle, "default",  
                    SND_PCM_STREAM_PLAYBACK, 0);  
  if (rc < 0) {  
    fprintf(stderr,  
            "unable to open pcm device: %s\n",  
            snd_strerror(rc));  
    exit(1);  
  } else{
    printf("open device sucess\n");
  } 

  /* Allocate a hardware parameters object. */  
  snd_pcm_hw_params_alloca(¶ms);  

  /* Fill it in with default values. */  
  snd_pcm_hw_params_any(handle, params);  

  /* Set the desired hardware parameters. */  

  /* Interleaved mode */  
  snd_pcm_hw_params_set_access(handle, params,  
                      SND_PCM_ACCESS_RW_INTERLEAVED);  

  /* Signed 16-bit little-endian format */  
  snd_pcm_hw_params_set_format(handle, params,  
                              SND_PCM_FORMAT_S16_LE);  

  /* Two channels (stereo) */  
  snd_pcm_hw_params_set_channels(handle, params, 2);  

  /* 11025 bits/second sampling rate (CD quality) */  
  val = 11025;  
  snd_pcm_hw_params_set_rate_near(handle, params,  
                                  &val, &dir);  

  /* Set period size to 32 frames. */  
 // frames = 32;  
 // snd_pcm_hw_params_set_period_size_near(handle,  
 //                             params, &frames, &dir);  

  /* Write the parameters to the driver */  
  rc = snd_pcm_hw_params(handle, params);  
  if (rc < 0) {  
    fprintf(stderr,  
            "unable to set hw parameters: %s\n",  
            snd_strerror(rc));  
    exit(1);  
  }  

  /* Use a buffer large enough to hold one period */  
  snd_pcm_hw_params_get_period_size(params, &frames,  
                                    &dir);  
  printf("frames = %ld\n",frames);
  size = frames * 4; /* 2 bytes/sample, 2 channels */  
  buffer = (char *) malloc(size);  

  /* We want to loop for 5 seconds */  
  snd_pcm_hw_params_get_period_time(params,  
                                    &val, &dir);  

  fd  = open(argv[1],O_RDONLY);
  if(fd<0){
    printf("open error\n");
  }
  while (1) {  
    rc = read(fd, buffer, size);
    if (rc == 0) {  
      fprintf(stderr, "end of file on input\n");  
      break;  
    } else if (rc != size) {  
      fprintf(stderr,  
              "short read: read %d bytes\n", rc);  
    }
    while((rc = snd_pcm_writei(handle, buffer, frames)<0)) 
   {
         usleep(2000); 
         if (rc == -EPIPE)
        {
          /* EPIPE means underrun */
          fprintf(stderr, "underrun occurred\n");
          snd_pcm_prepare(handle);
         }
         else if (rc < 0)
         {
             fprintf(stderr,
              "error from writei: %s\n",
              snd_strerror(rc));
         }
    }
  }  
  close(fd);
  snd_pcm_drain(handle);  
  snd_pcm_close(handle);  
  free(buffer);  

  return 0;  
}  

函数中已经有相关的注释了。播放原始音频数据可以认为有三部分组成:
第一:参数设置阶段。这一阶段需要设置波特率,帧率,通道数等。
第二:读文件阶段。
第三:播放阶段。主要是snd_pcm_writei想pcm设备写入数据了。
编译:
gcc -o player audio_palyer.c -lasound
运行:
./player audio.pcm
播放的就是我们自己录制的音频数据,可以验证我们的录音程序是否正常。

播放wav格式的音频文件

这里要注意:wav是文件的格式,pcm是没有压缩的音频的编码格式。也就是说,一个是文件封装格式,一个是视频的编码格式,两个是有本质的区别的。在我们的例子中,wav文件的数据区,存储的正式pcm编码格式的音频数据。
头文件:palywav.h

#ifndef PLAY_WAV_H
#define PLAY_WAV_H

#include
#include
#include 
#include "alsa/asoundlib.h"

struct WAV_HEADER
{
    char rld[4]; //riff 标志符号
    int rLen; 
    char wld[4]; //格式类型(wave)
    char fld[4]; //"fmt"

    int fLen; //sizeof(wave format matex)

    short wFormatTag; //编码格式
    short wChannels; //声道数
    int nSamplesPersec ; //采样频率
    int nAvgBitsPerSample;//WAVE文件采样大小
    short wBlockAlign; //块对齐
    short wBitsPerSample; //WAVE文件采样大小

    char dld[4]; //”data“
    int wSampleLength; //音频数据的大小

} ;

int set_pcm_play(char * filename);

#endif

源文件:playwav.c

#include "play_wav.h"
int set_pcm_play(char * filename)
{
        int rc;
        int ret;
        int size;
        snd_pcm_t* handle; //pcm文件句柄
        snd_pcm_hw_params_t* params;//硬件参数结构体
        unsigned int val;
        int dir=0;
        snd_pcm_uframes_t frames;
    struct WAV_HEADER wav_header;
        char *buffer;
        int channels;
        int frequency;
        int bit;
        int datablock;
    int nread;
   //unsigned char ch[100]; 
        FILE *fp;

        //首先打开wav文件
        fp=fopen(filename,"rb");
    if(fp==NULL)
    {
        perror("open file failed:\n");
        exit(1);
    }   
    //读取头部信息    
    nread=fread(&wav_header,1,sizeof(wav_header),fp);
    printf("fread byte is:%d\n",nread);
        //通过读取到的头部信息,初始化硬件参数
        channels=wav_header.wChannels;
        frequency=wav_header.nSamplesPersec;
        bit=wav_header.wBitsPerSample;
        datablock=wav_header.wBlockAlign;
       //打开pcm设备
        rc=snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
        if(rc<0)
        {
                perror("\nopen PCM device failed:");
                exit(1);
        }

        //分配一个硬件参数结构体
        snd_pcm_hw_params_alloca(¶ms); 
        if(rc<0)
        {
                perror("\nsnd_pcm_hw_params_alloca:");
                exit(1);
        }
        //使用默认值初始化
         rc=snd_pcm_hw_params_any(handle, params);
        if(rc<0)
        {
                perror("\nsnd_pcm_hw_params_any:");
                exit(1);
        }
        rc=snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); //交错模式
        if(rc<0)
        {
                perror("\nsed_pcm_hw_set_access:");
                exit(1);

        }

        //选择音频数据格式。
        switch(bit/8)
        {
        case 1:snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_U8);
                break ;
        case 2:snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
                break ;
        case 3:snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S24_LE);
                break ;

        }
        rc=snd_pcm_hw_params_set_channels(handle, params, channels); //设置通道数
        if(rc<0)
        {
                perror("\nsnd_pcm_hw_params_set_channels:");
                exit(1);
        }
        val = frequency;
        rc=snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir); //设置比特率
        if(rc<0)
        {
                perror("\nsnd_pcm_hw_params_set_rate_near:");
                exit(1);
        }
        //设置硬件参数
        rc = snd_pcm_hw_params(handle, params);
        if(rc<0)
        {
        perror("\nsnd_pcm_hw_params: ");
        exit(1);
        }
        //获取帧率
        rc=snd_pcm_hw_params_get_period_size(params, &frames, &dir);
        if(rc<0)
        {
                perror("\nsnd_pcm_hw_params_get_period_size:");
                exit(1);
        }
        //通过帧率计算缓冲区的大小
        size = frames * datablock;

        buffer =(char*)malloc(size);
    fseek(fp,58,SEEK_SET); //定位到数据区
    //一下的循环就是不断的从数据区读数据出来,然后写入pcm设备中,从而播放出来
    while (1)
        {
                memset(buffer,0,sizeof(buffer));
                ret = fread(buffer, 1, size, fp);
                if(ret == 0)
                {
                        printf("song write in completed\n");
                        break;
                }
                 else if (ret != size)
                {
                 }
        while((ret = snd_pcm_writei(handle, buffer, frames)<0)) 
           {
                 usleep(2000); 
                 if (ret == -EPIPE)
                {
                  /* EPIPE means underrun */
                  fprintf(stderr, "underrun occurred\n");
                  snd_pcm_prepare(handle);
                 }
                 else if (ret < 0)
                 {
                          fprintf(stderr,
                      "error from writei: %s\n",
                      snd_strerror(ret));
                 }
            }

    }
        fclose(fp);
        snd_pcm_drain(handle);
        snd_pcm_close(handle);
        free(buffer);
        return 0;
}

int main(int argc,char **argv){
    if(argc !=2){
        printf("usage:./playwav xxx.wav");
        return -1;
    }
    set_pcm_play(argv[1]);
return 0;
}

函数中已经有相关的注释了。播放wav可以认为有四部分组成:
第一:解析文件头。
第二:参数设置阶段。这一阶段需要通过解析文件头得到的信息,来设置波特率,帧率,通道数等。
第二:读音频数据阶段。
第三:播放阶段。主要是snd_pcm_writei想pcm设备写入数据了。
编译:
gcc play_wav.c -lasound
执行:
./a.out song.wav

你可能感兴趣的:(ffmpeg,ubuntu,录音,wav,alsa,libasound)