上一篇博客,我们把一个Yuv编码格式的视频文件编码为H264格式。那么接下来,自然要学习下音频编码了。在学习音频编码之前,我们先看看ubuntu下如何采集声音和播放声音。
录音
录制5秒钟的一段音频。
audio_recorder.c:
/*created by Jinwei Liu*/
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>
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 <alsa/asoundlib.h>
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<stdio.h>
#include<stdlib.h>
#include <string.h>
#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