對於一個wave文件,若是須要播放,涉及到幾個方面html
1.對於wave文件的解析linux
2.經過解析wave文件,將獲得的參數(主要是sampfrequency, bitsperSample,channel)經過alsa api設下去ios
3.正確找到data的起始點api
4.play alsaapp
1.對於wave文件的解析,須要知道wave文件的格式async
注意幾點,標準的是44byte的頭,可是有些狀況下會有additional info, 佔據2字節。頭信息參見下圖,也能夠參考wave 文件解析ide
endianoop |
field name測試 |
Sizeui |
|
big | ChunkID | 4 | 文件頭標識,通常就是" RIFF" 四個字母 |
little | ChunkSize | 4 | 整個數據文件的大小,不包括上面ID和Size自己 |
big | Format | 4 | 通常就是" WAVE" 四個字母 |
big | SubChunk1ID | 4 | 格式說明塊,本字段通常就是"fmt " |
little | SubChunk1Size | 4 | 本數據塊的大小,不包括ID和Size字段自己 |
little | AudioFormat | 2 | 音頻的格式說明 |
little | NumChannels | 2 | 聲道數 |
little | SampleRate | 4 | 採樣率 |
little | ByteRate | 4 | 比特率,每秒所須要的字節數 |
little | BlockAlign | 2 | 數據塊對齊單元 |
little | BitsPerSample | 2 | 採樣時模數轉換的分辨率 |
big | SubChunk2ID | 4 | 真正的聲音數據塊,本字段通常是"data" |
little | SubChunk2Size | 4 | 本數據塊的大小,不包括ID和Size字段自己 |
little | Data | N | 音頻的採樣數據 |
2.設置alsa的參數能夠詳見代碼
3.經過解析wave file能夠知道咱們data的起始位置
4.經過alsa來play,詳見代碼
注意點:仔細點...指針操道別搞錯了..
代碼適合初學者看..
alsa的軟件體系結構
Alsa播放須要遵循一套規定,包括設置alsa的open,samplerate,bitrate(format,涉及到大端小端),period,access,channel,prepare,這是一套相對來講固定格式的準備工做。
在打開alsa 設備的時候須要指定具體打開哪一個alsa設備,如同aplay的時候咱們須要用 -D 去指定device,例如 -D hw:0,0, -D sd_out_16k
關於這個device,能夠經過 aplay/arecord -l 去查看
(注意 -L 和-l 是兩個命令一個是list pcms 一個是list device, 其中咱們指定的sd_out_16k的別名就會在-L命令中被顯示出來)
這是aplay -l
對於指定alsa device這個事情,能夠在2個文件中配置,一個是asound.conf 一個是.asoundrc. 若是已經安裝了alsa,那麼打開/usr/share/alsa/alsa.conf就能夠看到
對於這兩個文件,是preload的,能夠經過修改這兩個文件來制定你想要的pcm別名.可是若是若是你沒作過任何修改,這讓兩個文件是不存在的,也就是說你須要本身去建立.下面這個是我本身建立的在home目錄下的.asoundrc文件,僅僅只是用來作這個測試,指定了device,plugin的hw,sample frequency是16000,具體能夠百度
PS:在alsa說明文檔有這麼一句話
The most important ALSA interfaces to the PCM devices are the "plughw" and the "hw" interface. If you use the "plughw" interface, you need not care much about the sound hardware. If your soundcard does not support the sample rate or sample format you specify, your data will be automatically converted. This also applies to the access type and the number of channels. With the "hw" interface, you have to check whether your hardware supports the configuration you would like to use.
/* Name of the PCM device, like plughw:0,0 */ /* The first number is the number of the soundcard, */ /* the second number is the number of the device. */ char *pcm_name;
Then we initialize the variables and allocate a hwparams structure:
/* Init pcm_name. Of course, later you */ /* will make this configurable ;-) */ pcm_name = strdup("plughw:0,0");
/* Allocate the snd_pcm_hw_params_t structure on the stack. */ snd_pcm_hw_params_alloca(&hwparams);
指定了alsa device以後,你能夠經過aplay來試一下是否可使用
例如:
aplay -D hw:0,0 /home/root/left_1k_right_400hz.wav
aplay -r 16000 -f S16_LE -D sd_out_16k -c 2 left_1k_right_400hz.wav
接下去就是對於alsadevice 的打開,能夠參見ALSA_HOWTO裏面說的仍是挺詳細的.
注意access設置的時候有一段說明
/* Open PCM. The last parameter of this function is the mode. */ /* If this is set to 0, the standard mode is used. Possible */ /* other values are SND_PCM_NONBLOCK and SND_PCM_ASYNC. */ /* If SND_PCM_NONBLOCK is used, read / write access to the */ /* PCM device will return immediately. If SND_PCM_ASYNC is */ /* specified, SIGIO will be emitted whenever a period has */ /* been completely processed by the soundcard. */ if (snd_pcm_open(&pcm_handle, pcm_name, stream, 0) < 0) { fprintf(stderr, "Error opening PCM device %s\n", pcm_name); return(-1);
其餘具體順序參見代碼 bool PrepareAlsaDevice(Alsa_Conf* palsaCfg,snd_pcm_t** phandler);
對於別的alsa代碼,一樣的一套方式能夠去使用,由於裏面我使用的都是palsaCfg傳進來的configuration,因此之後若是使用則只須要修改configuration的值就好.
對於play alsa,有幾種方式
能夠用於傳輸interleaved/non-interleaved的數據,而且有阻塞和非阻塞兩種模式。在代碼裏面我用的是這種,對於write,根據你的interleaved/non-interleaved來肯定究竟是用writei仍是writen。
我用的是writei,只須要把數據灌給alsa設備就能夠了。具體方式見代碼。
能夠用於傳輸interleaved/non-interleaved/complex的數據。正在寫sample code.基於HOW_TO的samplecode本身改了改...能夠直接用,由於wave文件自己LR兩個channel都放好了,因此2中copy方式均可以,詳見下面的示例代碼
未完待續....
/** @file TestAlsaPlayWave.cpp @brief This is a short example to play the audio wave, please define the path in the main func @par author: jlm @par pre env: alsa @todo */ #include <stdio.h> #include <stdlib.h> #include <string> #include <sched.h> #include <errno.h> #include <getopt.h> #include <iostream> #include <asoundlib.h> #include <sys/time.h> #include <math.h> using namespace std; /*********Type definition***********************/ typedef unsigned char uint8; typedef unsigned short uint16; typedef enum EBitsPerSample { BITS_UNKNOWN = 0, BITS_PER_SAMPLE_8 = 8, BITS_PER_SAMPLE_16 = 16, BITS_PER_SAMPLE_32 = 32 }EBitsPerSample_t; typedef enum ENumOfChannels { NUM_OF_CHANNELS_1 = 1, NUM_OF_CHANNELS_2 = 2 }ENumOfChannels_t; #if 0 /** PCM state */ typedef enum _snd_pcm_state { /** Open */ SND_PCM_STATE_OPEN = 0, /** Setup installed */ SND_PCM_STATE_SETUP, /** Ready to start */ SND_PCM_STATE_PREPARED, /** Running */ SND_PCM_STATE_RUNNING, /** Stopped: underrun (playback) or overrun (capture) detected */ SND_PCM_STATE_XRUN, /** Draining: running (playback) or stopped (capture) */ SND_PCM_STATE_DRAINING, /** Paused */ SND_PCM_STATE_PAUSED, /** Hardware is suspended */ SND_PCM_STATE_SUSPENDED, /** Hardware is disconnected */ SND_PCM_STATE_DISCONNECTED, SND_PCM_STATE_LAST = SND_PCM_STATE_DISCONNECTED } snd_pcm_state_t; #endif typedef struct ALSA_CONFIGURATION { std::string alsaDevice; std::string friendlyName; /// Read: Buffer size should be large enough to prevent overrun (read / write buffer full) unsigned int alsaBufferSize; /// Chunk size should be smaller to prevent underrun (write buffer empty) unsigned int alsaPeriodFrame; unsigned int samplingFrequency;//48kHz EBitsPerSample bitsPerSample;//16 ENumOfChannels numOfChannels; bool block; // false means nonblock snd_pcm_access_t accessType; snd_pcm_stream_t streamType; // Playback or capture unsigned int alsaCapturePeriod; // Length of each capture period }Alsa_Conf; typedef struct Wave_Header { uint8 ChunkID[4]; uint8 ChunkSize[4]; uint8 Format[4]; uint8 SubChunk1ID[4]; uint8 SubChunk1Size[4]; uint8 AudioFormat[2]; uint8 NumChannels[2]; uint8 SampleRate[4]; uint8 ByteRate[4]; uint8 BlockAlign[2]; uint8 BitsPerSample[2]; uint8 CombineWaveFileExtra2Bytes[2]; uint8 SubChunk2ID[4]; uint8 SubChunk2Size[4]; }Wave_Header_t; typedef struct Wave_Header_Size_Info { uint8 ChunkID[4]; uint8 ChunkSize[4]; uint8 Format[4]; uint8 SubChunk1ID[4]; uint8 SubChunk1Size[4]; }Wave_Header_Size_Info_t; typedef struct Wave_Header_Audio_Info { uint8 AudioFormat[2]; uint8 NumChannels[2]; uint8 SampleRate[4]; uint8 ByteRate[4]; uint8 BlockAlign[2]; uint8 BitsPerSample[2]; }Wave_Header_Audio_Info_t; typedef struct Wave_Header_Additional_Info { uint8 AdditionalInfo_2Bytes[2]; //this depends on the SubChunk1Size,normal if SubChunk1Size=16 then match the normal wave format, if SubChunk1Size=18 then 2 more additional info bytes }Wave_Header_Additional_Info_t; typedef struct Wave_Header_Data_Info { uint8 SubChunk2ID[4]; uint8 SubChunk2Size[4]; }Wave_Header_Data_Info_t; /*********Global Variable***********************/ snd_pcm_uframes_t g_frames; //just test purpose /*********Func Declaration***********************/ void TestAlsaDevice(snd_pcm_t** phandler); bool PrepareAlsaDevice(Alsa_Conf* palsaCfg,snd_pcm_t** phandler); bool closeAlsaDevice(snd_pcm_t** phandler); bool ParseWaveFile(const string wavepath,Alsa_Conf* palsaCfg); uint16 HandleLittleEndian(uint8* arr,int size); bool PlayWave(FILE** fp,snd_pcm_t** phandler,Alsa_Conf* palsaCfg); uint16 HandleLittleEndian(uint8* arr,int size) { uint16 value=0; for(int i=0;i<size;i++) { value=value+(arr[i]<<(8*i)); } return value; } #if 0 //this is the return value ChunkID = "RIFF" ChunkSize = 54310 Format = "WAVE" fmt = "fmt " SubChunk1Size = 18 AudioFormat = 1 NumChannels = 2 SampleRate = 16000 ByteRate = 64000 BlockAlign = 4 BitsPerSample = 16 SubChunk2ID = "data" SubChunk2Size = 54272 #endif //parse the wave file bool ParseWaveFile(const string wavepath,Alsa_Conf* palsaCfg,FILE** fp) { int ret=0; //FILE* fp=NULL; *fp=fopen(wavepath.c_str(),"rb"); if(*fp==NULL) { cout<<"Can parse the wave file-->need check the file name"<<endl; } /*********************size info parse begin*************************/ //read size info Wave_Header_Size_Info_t wav_size_info; memset(&wav_size_info,0,sizeof(Wave_Header_Size_Info_t)); ret=fread(&wav_size_info,sizeof(Wave_Header_Size_Info_t),1,*fp); if(ret<1) { cout<<"read error"<<endl; return false; } string ChunkID=""; for(int i=0;i<4;i++) { ChunkID+=wav_size_info.ChunkID[i]; } string riff="RIFF"; if(0!=strcmp(ChunkID.c_str(),riff.c_str())) { cout<<"Invalid the fist Chunk id"<<endl; return false; } uint16 ChunkSize=HandleLittleEndian(wav_size_info.ChunkSize,4); cout<<"The ChunSize is "<<ChunkSize<<endl; string Format=""; for(int i=0;i<4;i++) { Format+=wav_size_info.Format[i]; } if(0!=strcmp(Format.c_str(),"WAVE")) { cout<<"Invalid the format"<<endl; return false; } string SubChunk1ID=""; for(int i=0;i<4;i++) { SubChunk1ID+=wav_size_info.SubChunk1ID[i]; } string fmt="fmt "; if(0!=strcmp(SubChunk1ID.c_str(),fmt.c_str())) { cout<<"Invalid the SubChunk1ID "<<endl; return false; } uint16 SubChunk1Size=HandleLittleEndian(wav_size_info.SubChunk1Size,4); if(SubChunk1Size!=16 && SubChunk1Size!=18) { cout<<"Invalid the SubChunk1Size"<<endl; return false; } /*********************Audio info parse begin*************************/ Wave_Header_Audio_Info_t wav_audio_info; memset(&wav_audio_info,0,sizeof(Wave_Header_Audio_Info_t)); ret=fread(&wav_audio_info,sizeof(Wave_Header_Audio_Info_t),1,*fp); if(ret<1) { cout<<"read error"<<endl; return false; } //fseek(fp,sizeof(Wave_Header_Size_Info_t),0);//文件指針偏移3個字節到'2' because fread will shift the pointer uint16 AudioFormat =HandleLittleEndian(wav_audio_info.AudioFormat,2); uint16 NumChannels =HandleLittleEndian(wav_audio_info.NumChannels,2); uint16 SampleRate =HandleLittleEndian(wav_audio_info.SampleRate,4); uint16 ByteRate =HandleLittleEndian(wav_audio_info.ByteRate,4); uint16 BlockAlign =HandleLittleEndian(wav_audio_info.BlockAlign,2); uint16 BitsPerSample=HandleLittleEndian(wav_audio_info.BitsPerSample,2); palsaCfg->numOfChannels=(ENumOfChannels)NumChannels; palsaCfg->samplingFrequency=SampleRate; palsaCfg->bitsPerSample=(EBitsPerSample)BitsPerSample; /*********************Additional info parse begin if needed*************************/ if(SubChunk1Size==18) { Wave_Header_Additional_Info_t wav_additional_info; memset(&wav_additional_info,0,sizeof(Wave_Header_Additional_Info_t)); fread(&wav_additional_info,sizeof(Wave_Header_Additional_Info_t),1,*fp); cout<<"read wav_additional_info"<<endl; if(ret<1) { cout<<"read error"<<endl; return false; } uint16 AdditionalInfo=HandleLittleEndian(wav_additional_info.AdditionalInfo_2Bytes,2); cout<<"read AdditionalInfo value="<<AdditionalInfo<<endl; } /*********************Data info parse begin *************************/ Wave_Header_Data_Info_t wave_data_info; memset(&wave_data_info,0,sizeof(Wave_Header_Data_Info_t)); fread(&wave_data_info,sizeof(Wave_Header_Data_Info_t),1,*fp); if(ret<1) { cout<<"read error"<<endl; return false; } string SubChunk2ID=""; for(int i=0;i<4;i++) { SubChunk2ID+=wave_data_info.SubChunk2ID[i]; } string fact="fact"; string data="data"; if(0==strcmp(SubChunk2ID.c_str(),fact.c_str())) { cout<<"SubChunk2ID fact"<<endl; } else if(0==strcmp(SubChunk2ID.c_str(),data.c_str())) { cout<<"SubChunk2ID data"<<endl; } else { cout<<"Invalid SubChunk2ID "<<endl; return false; } uint16 SubChunk2Size=HandleLittleEndian(wave_data_info.SubChunk2Size,4); cout<<"End Parse"<<endl; return true; } bool PlayWave(FILE** fp,snd_pcm_t** phandler,Alsa_Conf* palsaCfg) { int err=0; bool ret=false; snd_pcm_uframes_t frames=palsaCfg->alsaPeriodFrame; int bytesPerFrame=(int)palsaCfg->numOfChannels*palsaCfg->bitsPerSample/8; //4bytes uint16 audio_data_size=frames*bytesPerFrame;//one period 10ms ,1600*10/1000*(2*16/8)=640bytes one period uint8* buffer=new uint8[audio_data_size]; cout<<"Start play wave"<<endl; if(*fp==NULL || *phandler==NULL || palsaCfg==NULL) { cout<<"End play wave because something is NULL"<<endl; return false; } //fseek(*fp,46,SEEK_SET); //no need to do fseek because already shifted cout<<"available frame "<<snd_pcm_avail(*phandler)<<"my frames is "<<frames<<endl; while(true) { if(feof(*fp)) { cout<<"Reach end of the file"<<endl; break; } else { if(snd_pcm_avail(*phandler)<frames) { continue; } else { memset(reinterpret_cast<void*>(buffer),0,sizeof(uint8)*audio_data_size); err=fread(buffer,sizeof(uint8)*audio_data_size,1,*fp); if(err=0) { cout<<"read error"<<endl; } if ( NULL != buffer ) { err = snd_pcm_writei(*phandler, buffer, frames); if (err < 0) { cout<<"Fail to write the audio data to ALSA. Reason: "<<(snd_strerror(err)); // recover ALSA device err = snd_pcm_recover(*phandler, err, 0); if (err < 0) { cout<<"Fail to recover ALSA device. Reason: "<<(snd_strerror(err)); ret = false; } else { cout<<"ALSA device is recovered from error state"<<endl; } } } else { cout<<"Write buffer is NULL!"<<endl; } } } usleep(palsaCfg->alsaCapturePeriod / (2 * 1000)); } delete[] buffer; buffer=NULL; return ret; } bool PrepareAlsaDevice(Alsa_Conf* palsaCfg,snd_pcm_t** phandler) { bool ret=false; bool success=true; int error=0; snd_pcm_format_t format; snd_pcm_hw_params_t *hw_params = NULL; int dir=0; if(palsaCfg!=NULL) { // open ALSA device error=snd_pcm_open(phandler,palsaCfg->alsaDevice.c_str(),palsaCfg->streamType,palsaCfg->block? 0:SND_PCM_NONBLOCK); if(error<0) //0 on success otherwise a negative error code { success=false; cout<<"Open Alsadevice error error code="<<snd_strerror(error)<<endl; } if(success) { //allocate hardware parameter structur error=snd_pcm_hw_params_malloc(&hw_params);//alsao can use snd_pcm_hw_params_alloca(&hwparams) if(error<0) { success=false; hw_params=NULL; cout<<"Set hw params error error code="<<snd_strerror(error)<<endl; } } if(success) { //Fill params with a full configuration space for a PCM. initialize the hardware parameter error=snd_pcm_hw_params_any(*phandler,hw_params); if (error < 0) { success=false; cout<<"Broken configuration for PCM: no configurations available: "<<snd_strerror(error)<<endl; } } if(success) { // set the access type error = snd_pcm_hw_params_set_access(*phandler, hw_params, palsaCfg->accessType); if (error < 0) { cout<<"Fail to set access type. Reason: "<<snd_strerror(error)<<endl; success = false; } } if(success) { switch (palsaCfg->bitsPerSample) { case BITS_PER_SAMPLE_8: { format = SND_PCM_FORMAT_U8; break; } case BITS_PER_SAMPLE_16: { format = SND_PCM_FORMAT_S16_LE; //indicate this was little endian break; } case BITS_PER_SAMPLE_32: { format = SND_PCM_FORMAT_S32_LE; break; } default: { format = SND_PCM_FORMAT_S16_LE; cout<<"Invalid format"<<endl; success=false; } } if(success) { error=snd_pcm_hw_params_set_format(*phandler,hw_params,format); if(error<0) { cout<<"set format not available for "<<snd_strerror(error)<<endl; success=false; } } } if(success) { error=snd_pcm_hw_params_set_rate_near(*phandler,hw_params,&palsaCfg->samplingFrequency,0); if(error<0) { cout<<"set rate not available for "<<snd_strerror(error)<<endl; success=false; } } if(success) { error=snd_pcm_hw_params_set_channels(*phandler,hw_params,palsaCfg->numOfChannels); if(error<0) { cout<<"set_channels not available for "<<snd_strerror(error)<<endl; success=false; } } if (success) { // set period size (period size is also a chunk size for reading from ALSA) snd_pcm_uframes_t alsaPeriodFrame = static_cast<snd_pcm_uframes_t>(palsaCfg->alsaPeriodFrame); // One frame could be 4 bytes at most // set period size error = snd_pcm_hw_params_set_period_size_near(*phandler, hw_params, &alsaPeriodFrame, &dir); if (error < 0) { cout<<"Fail to set period size. Reason: "<<snd_strerror(error)<<endl; success = false; } } if (success) { // set hardware parameters error = snd_pcm_hw_params(*phandler, hw_params); if (error < 0) { cout<<"Fail to set hardware parameter. Reason: "<<snd_strerror(error)<<endl; success = false; } } if (success) { error=snd_pcm_hw_params_get_period_size(hw_params, &g_frames, &dir); //get frame cout<<"Frame is "<<g_frames<<endl; // free the memory for hardware parameter structure snd_pcm_hw_params_free(hw_params); hw_params = NULL; // Prepare ALSA device error = snd_pcm_prepare(*phandler); if (error < 0) { cout<<"Fail to prepare ALSA device. Reason: "<<(snd_strerror(error))<<endl; success = false; } } if (success) { cout<<"ALSA device is ready to use"<<endl; } else { // fail to prepare ALSA device ==> un-initialize ALSA device if (hw_params != NULL) { snd_pcm_hw_params_free(hw_params); hw_params = NULL; } closeAlsaDevice(phandler); } } return success; } bool closeAlsaDevice(snd_pcm_t** phandler) { bool ret = true; snd_pcm_state_t state; int snd_ret; if (*phandler != NULL) { // drop the pending audio frame if needed state = snd_pcm_state(*phandler); cout<<"Alsa handler sate: "<<state<<endl; if ((SND_PCM_STATE_RUNNING == state) || (SND_PCM_STATE_XRUN == state) || (SND_PCM_STATE_SUSPENDED == state)) { snd_ret = snd_pcm_drop(*phandler); if (snd_ret < 0) { cout<<"Fail to drop ALSA device. Reason: "<<(snd_strerror(snd_ret))<<endl; } } // close ALSA handler snd_ret = snd_pcm_close(*phandler); if (snd_ret < 0) { cout<<"Fail to close ALSA device. Reason: "<<(snd_strerror(snd_ret))<<endl; ret = false; } *phandler = NULL; cout<<"CLOSE ALSA DEVICE"<<endl; } return ret; } int main() { bool ret=false; snd_pcm_t* m_phandler=NULL; Alsa_Conf* m_palsaCfg=new Alsa_Conf(); m_palsaCfg->alsaDevice = string("sd_out_16k"); //m_palsaCfg->samplingFrequency = 16000; m_palsaCfg->alsaCapturePeriod = 50; //m_palsaCfg->numOfChannels = NUM_OF_CHANNELS_1; m_palsaCfg->block = true; //block m_palsaCfg->friendlyName = "AlsaWave"; //m_palsaCfg->bitsPerSample = BITS_PER_SAMPLE_16; m_palsaCfg->alsaPeriodFrame = m_palsaCfg->samplingFrequency * m_palsaCfg->alsaCapturePeriod / 1000; // calculate the number of frame in one period m_palsaCfg->alsaBufferSize = m_palsaCfg->alsaPeriodFrame * 8; //means the whole buffer was perdion*8, e.g. 10ms means every 10ms we will get/send the data m_palsaCfg->accessType = SND_PCM_ACCESS_RW_INTERLEAVED; m_palsaCfg->streamType = SND_PCM_STREAM_PLAYBACK; FILE* fp=NULL; const string wavePath="/mnt/hgfs/0_SharedFolder/0_Local_Test_Folder/01_TestFolder/TestALSA/left_1k_right_400hz.wav"; //parse the wave file ret=ParseWaveFile(wavePath,m_palsaCfg,&fp); //update the value m_palsaCfg->alsaPeriodFrame = m_palsaCfg->samplingFrequency * m_palsaCfg->alsaCapturePeriod / 1000; // calculate the number of frame in one period if(ret) { //open alsa device ret=PrepareAlsaDevice(m_palsaCfg,&m_phandler); } if(ret) { PlayWave(&fp,&m_phandler,m_palsaCfg); } closeAlsaDevice(&m_phandler); if(fp!=NULL) { fclose(fp); fp=NULL; } delete m_palsaCfg; m_palsaCfg=NULL; return 0; }
/**@file TestAlsaPlayWave.cpp @brief This is a short example to play the audio wave, please define the path in the main func @par author: jlm @par pre env: alsa @todo*/ #include <stdio.h> #include <stdlib.h> #include <string> #include <sched.h> #include <errno.h> #include <getopt.h> #include "asoundlib.h" #include <sys/time.h> #include <math.h> #include <iostream> using namespace std; /*********Type definition***********************/ typedef unsigned char uint8; typedef unsigned short uint16; typedef unsigned int uint32; typedef enum EBitsPerSample { BITS_UNKNOWN = 0, BITS_PER_SAMPLE_8 = 8, BITS_PER_SAMPLE_16 = 16, BITS_PER_SAMPLE_32 = 32 }EBitsPerSample_t; typedef enum ENumOfChannels { NUM_OF_CHANNELS_1 = 1, NUM_OF_CHANNELS_2 = 2 }ENumOfChannels_t; #if 0 /** PCM state */ typedef enum _snd_pcm_state { /** Open */ SND_PCM_STATE_OPEN = 0, /** Setup installed */ SND_PCM_STATE_SETUP, /** Ready to start */ SND_PCM_STATE_PREPARED, /** Running */ SND_PCM_STATE_RUNNING, /** Stopped: underrun (playback) or overrun (capture) detected */ SND_PCM_STATE_XRUN, /** Draining: running (playback) or stopped (capture) */ SND_PCM_STATE_DRAINING, /** Paused */ SND_PCM_STATE_PAUSED, /** Hardware is suspended */ SND_PCM_STATE_SUSPENDED, /** Hardware is disconnected */ SND_PCM_STATE_DISCONNECTED, SND_PCM_STATE_LAST = SND_PCM_STATE_DISCONNECTED } snd_pcm_state_t; #endif typedef struct ALSA_CONFIGURATION { std::string alsaDevice; std::string friendlyName; /// Read: Buffer size should be large enough to prevent overrun (read / write buffer full) unsigned int alsaBufferSize; /// Chunk size should be smaller to prevent underrun (write buffer empty) unsigned int alsaPeriodFrame; unsigned int samplingFrequency;//48kHz EBitsPerSample bitsPerSample;//16 ENumOfChannels numOfChannels; bool block; // false means nonblock snd_pcm_access_t accessType; snd_pcm_stream_t streamType; // Playback or capture unsigned int alsaCapturePeriod; // Length of each capture period }Alsa_Conf; typedef struct Wave_Header { uint8 ChunkID[4]; uint8 ChunkSize[4]; uint8 Format[4]; uint8 SubChunk1ID[4]; uint8 SubChunk1Size[4]; uint8 AudioFormat[2]; uint8 NumChannels[2]; uint8 SampleRate[4]; uint8 ByteRate[4]; uint8 BlockAlign[2]; uint8 BitsPerSample[2]; uint8 CombineWaveFileExtra2Bytes[2]; uint8 SubChunk2ID[4]; uint8 SubChunk2Size[4]; }Wave_Header_t; typedef struct Wave_Header_Size_Info { uint8 ChunkID[4]; uint8 ChunkSize[4]; uint8 Format[4]; uint8 SubChunk1ID[4]; uint8 SubChunk1Size[4]; }Wave_Header_Size_Info_t; typedef struct Wave_Header_Audio_Info { uint8 AudioFormat[2]; uint8 NumChannels[2]; uint8 SampleRate[4]; uint8 ByteRate[4]; uint8 BlockAlign[2]; uint8 BitsPerSample[2]; }Wave_Header_Audio_Info_t; typedef struct Wave_Header_Additional_Info { uint8 AdditionalInfo_2Bytes[2]; //this depends on the SubChunk1Size,normal if SubChunk1Size=16 then match the normal wave format, if SubChunk1Size=18 then 2 more additional info bytes }Wave_Header_Additional_Info_t; typedef struct Wave_Header_Data_Info { uint8 SubChunk2ID[4]; uint8 SubChunk2Size[4]; }Wave_Header_Data_Info_t; static char *device = "sd_out_16k";//"plughw:0,0"; /* playback device */ static snd_pcm_format_t format = SND_PCM_FORMAT_S16; /* sample format */ static unsigned int rate = 44100; /* stream rate */ static unsigned int channels = 2; /* count of channels */ static unsigned int buffer_time = 50000; /* ring buffer length in us */ static unsigned int period_time = 10000; /* period time in us */ static double freq = 440; /* sinusoidal wave frequency in Hz */ static int verbose = 0; /* verbose flag */ static int resample = 1; /* enable alsa-lib resampling */ static int period_event = 0; /* produce poll event after each period */ static snd_pcm_sframes_t buffer_size; static snd_pcm_sframes_t period_size; static snd_output_t *output = NULL; uint16 HandleLittleEndian(uint8* arr,int size); //parse the wave file static bool ParseWaveFile(const string wavepath,Alsa_Conf* palsaCfg,FILE** fp); static int set_hwparams(snd_pcm_t *handle, snd_pcm_hw_params_t *params, snd_pcm_access_t access) { unsigned int rrate; snd_pcm_uframes_t size; int err, dir; /* choose all parameters */ err = snd_pcm_hw_params_any(handle, params); if (err < 0) { printf("Broken configuration for playback: no configurations available: %s\n", snd_strerror(err)); return err; } /* set hardware resampling */ err = snd_pcm_hw_params_set_rate_resample(handle, params, resample); if (err < 0) { printf("Resampling setup failed for playback: %s\n", snd_strerror(err)); return err; } /* set the interleaved read/write format */ err = snd_pcm_hw_params_set_access(handle, params, access); if (err < 0) { printf("Access type not available for playback: %s\n", snd_strerror(err)); return err; } /* set the sample format */ err = snd_pcm_hw_params_set_format(handle, params, format); if (err < 0) { printf("Sample format not available for playback: %s\n", snd_strerror(err)); return err; } /* set the count of channels */ err = snd_pcm_hw_params_set_channels(handle, params, channels); if (err < 0) { printf("Channels count (%i) not available for playbacks: %s\n", channels, snd_strerror(err)); return err; } /* set the stream rate */ rrate = rate; err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0); if (err < 0) { printf("Rate %iHz not available for playback: %s\n", rate, snd_strerror(err)); return err; } if (rrate != rate) { printf("Rate doesn't match (requested %iHz, get %iHz)\n", rate, err); return -EINVAL; } /* set the buffer time */ err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, &dir); if (err < 0) { printf("Unable to set buffer time %i for playback: %s\n", buffer_time, snd_strerror(err)); return err; } err = snd_pcm_hw_params_get_buffer_size(params, &size); if (err < 0) { printf("Unable to get buffer size for playback: %s\n", snd_strerror(err)); return err; } buffer_size = size; /* set the period time */ err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, &dir); if (err < 0) { printf("Unable to set period time %i for playback: %s\n", period_time, snd_strerror(err)); return err; } err = snd_pcm_hw_params_get_period_size(params, &size, &dir); if (err < 0) { printf("Unable to get period size for playback: %s\n", snd_strerror(err)); return err; } period_size = size; /* write the parameters to device */ err = snd_pcm_hw_params(handle, params); if (err < 0) { printf("Unable to set hw params for playback: %s\n", snd_strerror(err)); return err; } return 0; } static int set_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams) { int err; /* get the current swparams */ err = snd_pcm_sw_params_current(handle, swparams); if (err < 0) { printf("Unable to determine current swparams for playback: %s\n", snd_strerror(err)); return err; } /* start the transfer when the buffer is almost full: */ /* (buffer_size / avail_min) * avail_min */ err = snd_pcm_sw_params_set_start_threshold(handle, swparams, (buffer_size / period_size) * period_size); if (err < 0) { printf("Unable to set start threshold mode for playback: %s\n", snd_strerror(err)); return err; } /* allow the transfer when at least period_size samples can be processed */ /* or disable this mechanism when period event is enabled (aka interrupt like style processing) */ err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_event ? buffer_size : period_size); if (err < 0) { printf("Unable to set avail min for playback: %s\n", snd_strerror(err)); return err; } /* enable period events when requested */ if (period_event) { err = snd_pcm_sw_params_set_period_event(handle, swparams, 1); if (err < 0) { printf("Unable to set period event: %s\n", snd_strerror(err)); return err; } } /* write the parameters to the playback device */ err = snd_pcm_sw_params(handle, swparams); if (err < 0) { printf("Unable to set sw params for playback: %s\n", snd_strerror(err)); return err; } return 0; } /* * Underrun and suspend recovery */ static int xrun_recovery(snd_pcm_t *handle, int err) { if (verbose) printf("stream recovery\n"); if (err == -EPIPE) { /* under-run */ err = snd_pcm_prepare(handle); if (err < 0) printf("Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err)); return 0; } else if (err == -ESTRPIPE) { while ((err = snd_pcm_resume(handle)) == -EAGAIN) sleep(1); /* wait until the suspend flag is released */ if (err < 0) { err = snd_pcm_prepare(handle); if (err < 0) printf("Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err)); } return 0; } return err; } /* * Transfer method - direct write only */ static int direct_loop(snd_pcm_t *handle, signed short *samples ATTRIBUTE_UNUSED, snd_pcm_channel_area_t *areas ATTRIBUTE_UNUSED) { printf("JLM Test Direct Loop\r\n"); double phase = 0; const snd_pcm_channel_area_t *my_areas; snd_pcm_uframes_t offset, frames, size; snd_pcm_sframes_t avail, commitres; snd_pcm_state_t state; int err, first = 1; Alsa_Conf* m_palsaCfg=new Alsa_Conf(); FILE* fp=NULL; const string wavePath="/mnt/hgfs/0_SharedFolder/0_Local_Test_Folder/01_TestFolder/TestALSA/left_1k_right_400_60ms_16bit_SampleRate_1600_stero.wav"; //parse the wave file int ret=ParseWaveFile(wavePath,m_palsaCfg,&fp); while (1) { state = snd_pcm_state(handle); if (state == SND_PCM_STATE_XRUN) { err = xrun_recovery(handle, -EPIPE); if (err < 0) { printf("XRUN recovery failed: %s\n", snd_strerror(err)); return err; } first = 1; } else if (state == SND_PCM_STATE_SUSPENDED) { err = xrun_recovery(handle, -ESTRPIPE); if (err < 0) { printf("SUSPEND recovery failed: %s\n", snd_strerror(err)); return err; } } avail = snd_pcm_avail_update(handle); if (avail < 0) { err = xrun_recovery(handle, avail); if (err < 0) { printf("avail update failed: %s\n", snd_strerror(err)); return err; } first = 1; continue; } if (avail < period_size) { if (first) { first = 0; err = snd_pcm_start(handle); if (err < 0) { printf("Start error: %s\n", snd_strerror(err)); exit(EXIT_FAILURE); } else { printf("JLM start: %s with state=%d\n", snd_strerror(err),snd_pcm_state(handle)); } } else { err = snd_pcm_wait(handle, -1); if (err < 0) { if ((err = xrun_recovery(handle, err)) < 0) { printf("snd_pcm_wait error: %s\n", snd_strerror(err)); exit(EXIT_FAILURE); } first = 1; } } continue; } size = period_size; while (size > 0) { frames = size; err = snd_pcm_mmap_begin(handle, &my_areas, &offset, &frames); if (err < 0) { if ((err = xrun_recovery(handle, err)) < 0) { printf("MMAP begin avail error: %s\n", snd_strerror(err)); exit(EXIT_FAILURE); } first = 1; } //JLM edit { //printf("start to do jlm \r\n"); int bytesPerFrame=2*16/8; //4bytes uint16 audio_data_size=frames*bytesPerFrame;//one period 10ms ,1600*10/1000*(2*16/8)=640bytes one period //snd_pcm_channel_area_t *playWavareas=NULL; int stepsize=my_areas->step/8; uint8* buffer=new uint8[audio_data_size]; memset(reinterpret_cast<void*>(buffer),0,sizeof(uint8)*audio_data_size); if(fp!=NULL) { //printf("FP is NOT NULL jlm \r\n"); if(feof(fp)) { cout<<"Reach end of the file"<<endl; return 0; } err=fread(buffer,sizeof(uint8),audio_data_size,fp); if(err<0) { cout<<"read error"<<endl; fp=NULL; break; } #define method_2 #ifdef method_1 //printf("jlm audio_data_size=%d ,size=%d",audio_data_size,size); unsigned char* playAddress =(unsigned char*)my_areas->addr+my_areas->first/8+offset*stepsize; memcpy(playAddress,buffer,sizeof(uint8)*audio_data_size); #endif #ifdef method_2 // 0 stands for left, 1 stands for right, because 1 frame=4bytes and 1frame have 2samples ,left have 2 bytes, right have 2bytes uint8* playAddressLeft =(uint8*)my_areas[0].addr+my_areas[0].first/8+offset*stepsize; uint8* playAddressRight=(uint8*)my_areas[1].addr+my_areas[1].first/8+offset*stepsize; for(int i=0;i<frames;i++) { memcpy((playAddressLeft+stepsize*i),buffer+i*4,sizeof(uint8)*2); memcpy((playAddressRight+stepsize*i),buffer+i*4+2,sizeof(uint8)*2); } #endif } else { printf("FP is NULL jlm \r\n"); } delete [] buffer; buffer=NULL; } //generate_sine(my_areas, offset, frames, &phase); commitres = snd_pcm_mmap_commit(handle, offset, frames); if (commitres < 0 || (snd_pcm_uframes_t)commitres != frames) { if ((err = xrun_recovery(handle, commitres >= 0 ? -EPIPE : commitres)) < 0) { printf("MMAP commit error: %s\n", snd_strerror(err)); exit(EXIT_FAILURE); } first = 1; } size -= frames; } } } /* * */ struct transfer_method { const char *name; snd_pcm_access_t access; int (*transfer_loop)(snd_pcm_t *handle, signed short *samples, snd_pcm_channel_area_t *areas); }; static struct transfer_method transfer_methods[] = { //{ "write", SND_PCM_ACCESS_RW_INTERLEAVED, write_loop }, //{ "write_and_poll", SND_PCM_ACCESS_RW_INTERLEAVED, write_and_poll_loop }, //{ "async", SND_PCM_ACCESS_RW_INTERLEAVED, async_loop }, //{ "async_direct", SND_PCM_ACCESS_MMAP_INTERLEAVED, async_direct_loop }, { "direct_interleaved", SND_PCM_ACCESS_MMAP_INTERLEAVED, direct_loop }, //{ "direct_noninterleaved", SND_PCM_ACCESS_MMAP_NONINTERLEAVED, direct_loop }, //{ "direct_write", SND_PCM_ACCESS_MMAP_INTERLEAVED, direct_write_loop }, { NULL, SND_PCM_ACCESS_RW_INTERLEAVED, NULL } }; /****************************************************************************************** *************************************ALSA TEST *******************************************/ uint16 HandleLittleEndian(uint8* arr,int size) { uint16 value=0; for(int i=0;i<size;i++) { value=value+(arr[i]<<(8*i)); } return value; } //parse the wave file static bool ParseWaveFile(const string wavepath,Alsa_Conf* palsaCfg,FILE** fp) { int ret=0; //FILE* fp=NULL; *fp=fopen(wavepath.c_str(),"rb"); if(*fp==NULL) { cout<<"Can parse the wave file-->need check the file name"<<endl; } /*********************size info parse begin*************************/ //read size info Wave_Header_Size_Info_t wav_size_info; memset(&wav_size_info,0,sizeof(Wave_Header_Size_Info_t)); ret=fread(&wav_size_info,sizeof(Wave_Header_Size_Info_t),1,*fp); if(ret<1) { cout<<"read error"<<endl; return false; } string ChunkID=""; for(int i=0;i<4;i++) { ChunkID+=wav_size_info.ChunkID[i]; } string riff="RIFF"; if(0!=strcmp(ChunkID.c_str(),riff.c_str())) { cout<<"Invalid the fist Chunk id"<<endl; return false; } uint16 ChunkSize=HandleLittleEndian(wav_size_info.ChunkSize,4); cout<<"The ChunSize is "<<ChunkSize<<endl; string Format=""; for(int i=0;i<4;i++) { Format+=wav_size_info.Format[i]; } if(0!=strcmp(Format.c_str(),"WAVE")) { cout<<"Invalid the format"<<endl; return false; } string SubChunk1ID=""; for(int i=0;i<4;i++) { SubChunk1ID+=wav_size_info.SubChunk1ID[i]; } string fmt="fmt "; if(0!=strcmp(SubChunk1ID.c_str(),fmt.c_str())) { cout<<"Invalid the SubChunk1ID "<<endl; return false; } uint16 SubChunk1Size=HandleLittleEndian(wav_size_info.SubChunk1Size,4); if(SubChunk1Size!=16 && SubChunk1Size!=18) { cout<<"Invalid the SubChunk1Size"<<endl; return false; } /*********************Audio info parse begin*************************/ Wave_Header_Audio_Info_t wav_audio_info; memset(&wav_audio_info,0,sizeof(Wave_Header_Audio_Info_t)); ret=fread(&wav_audio_info,sizeof(Wave_Header_Audio_Info_t),1,*fp); if(ret<1) { cout<<"read error"<<endl; return false; } //fseek(fp,sizeof(Wave_Header_Size_Info_t),0);//文件指针å移3个å—节到'2' because fread will shift the pointer uint16 AudioFormat =HandleLittleEndian(wav_audio_info.AudioFormat,2); uint16 NumChannels =HandleLittleEndian(wav_audio_info.NumChannels,2); uint16 SampleRate =HandleLittleEndian(wav_audio_info.SampleRate,4); uint16 ByteRate =HandleLittleEndian(wav_audio_info.ByteRate,4); uint16 BlockAlign =HandleLittleEndian(wav_audio_info.BlockAlign,2); uint16 BitsPerSample=HandleLittleEndian(wav_audio_info.BitsPerSample,2); palsaCfg->numOfChannels=(ENumOfChannels)NumChannels; palsaCfg->samplingFrequency=SampleRate; palsaCfg->bitsPerSample=(EBitsPerSample)BitsPerSample; /*********************Additional info parse begin if needed*************************/ if(SubChunk1Size==18) { Wave_Header_Additional_Info_t wav_additional_info; memset(&wav_additional_info,0,sizeof(Wave_Header_Additional_Info_t)); fread(&wav_additional_info,sizeof(Wave_Header_Additional_Info_t),1,*fp); cout<<"read wav_additional_info"<<endl; if(ret<1) { cout<<"read error"<<endl; return false; } uint16 AdditionalInfo=HandleLittleEndian(wav_additional_info.AdditionalInfo_2Bytes,2); cout<<"read AdditionalInfo value="<<AdditionalInfo<<endl; } /*********************Data info parse begin *************************/ Wave_Header_Data_Info_t wave_data_info; memset(&wave_data_info,0,sizeof(Wave_Header_Data_Info_t)); fread(&wave_data_info,sizeof(Wave_Header_Data_Info_t),1,*fp); if(ret<1) { cout<<"read error"<<endl; return false; } string SubChunk2ID=""; for(int i=0;i<4;i++) { SubChunk2ID+=wave_data_info.SubChunk2ID[i]; } string fact="fact"; string data="data"; if(0==strcmp(SubChunk2ID.c_str(),fact.c_str())) { cout<<"SubChunk2ID fact"<<endl; } else if(0==strcmp(SubChunk2ID.c_str(),data.c_str())) { cout<<"SubChunk2ID data"<<endl; } else { cout<<"Invalid SubChunk2ID "<<endl; return false; } uint16 SubChunk2Size=HandleLittleEndian(wave_data_info.SubChunk2Size,4); cout<<"End Parse"<<endl; return true; } /*************************************ALSA TEST ******************************************* ******************************************************************************************/ #define ALSA_TEST_FROM_HOW_TO int main(int argc, char *argv[]) { //#ifdef ALSA_TEST_FROM_HOW_TO #if 1 struct option long_option[] = { {"help", 0, NULL, 'h'}, {"device", 1, NULL, 'D'}, {"rate", 1, NULL, 'r'}, {"channels", 1, NULL, 'c'}, {"frequency", 1, NULL, 'f'}, {"buffer", 1, NULL, 'b'}, {"period", 1, NULL, 'p'}, {"method", 1, NULL, 'm'}, {"format", 1, NULL, 'o'}, {"verbose", 1, NULL, 'v'}, {"noresample", 1, NULL, 'n'}, {"pevent", 1, NULL, 'e'}, {NULL, 0, NULL, 0}, }; snd_pcm_t *handle; int err, morehelp; snd_pcm_hw_params_t *hwparams; snd_pcm_sw_params_t *swparams; int method = 0; signed short *samples; unsigned int chn; snd_pcm_channel_area_t *areas; snd_pcm_hw_params_alloca(&hwparams); snd_pcm_sw_params_alloca(&swparams); morehelp = 0; while (1) { int c; if ((c = getopt_long(argc, argv, "hD:r:c:f:b:p:m:o:vne", long_option, NULL)) < 0) break; switch (c) { case 'h': morehelp++; break; case 'D': device = strdup(optarg); break; case 'r': rate = atoi(optarg); rate = rate < 4000 ? 4000 : rate; rate = rate > 196000 ? 196000 : rate; break; case 'c': channels = atoi(optarg); channels = channels < 1 ? 1 : channels; channels = channels > 1024 ? 1024 : channels; break; case 'f': freq = atoi(optarg); freq = freq < 50 ? 50 : freq; freq = freq > 5000 ? 5000 : freq; break; case 'b': buffer_time = atoi(optarg); buffer_time = buffer_time < 1000 ? 1000 : buffer_time; buffer_time = buffer_time > 1000000 ? 1000000 : buffer_time; break; case 'p': period_time = atoi(optarg); period_time = period_time < 1000 ? 1000 : period_time; period_time = period_time > 1000000 ? 1000000 : period_time; break; case 'm': for (method = 0; transfer_methods[method].name; method++) if (!strcasecmp(transfer_methods[method].name, optarg)) break; if (transfer_methods[method].name == NULL) method = 0; break; case 'o': { int tempformat; for (tempformat = 0; format < SND_PCM_FORMAT_LAST; tempformat++) { const char *format_name = snd_pcm_format_name((snd_pcm_format_t)tempformat); if (format_name) if (!strcasecmp(format_name, optarg)) break; } format=(snd_pcm_format_t)tempformat; if (format == SND_PCM_FORMAT_LAST) format = SND_PCM_FORMAT_S16; if (!snd_pcm_format_linear(format) && !(format == SND_PCM_FORMAT_FLOAT_LE || format == SND_PCM_FORMAT_FLOAT_BE)) { printf("Invalid (non-linear/float) format %s\n", optarg); return 1; } break; } case 'v': verbose = 1; break; case 'n': resample = 0; break; case 'e': period_event = 1; break; } } err = snd_output_stdio_attach(&output, stdout, 0); if (err < 0) { printf("Output failed: %s\n", snd_strerror(err)); return 0; } std::string alsaDevice="sd_out_16k"; printf("Playback device is %s\n", device); printf("Stream parameters are %iHz, %s, %i channels\n", rate, snd_pcm_format_name(format), channels); printf("Sine wave rate is %.4fHz\n", freq); printf("Using transfer method: %s\n", transfer_methods[method].name); if ((err = snd_pcm_open(&handle, alsaDevice.c_str(), SND_PCM_STREAM_PLAYBACK, 0)) < 0) { printf("Playback open error: %s\n", snd_strerror(err)); return 0; } if ((err = set_hwparams(handle, hwparams, transfer_methods[method].access)) < 0) { printf("Setting of hwparams failed: %s\n", snd_strerror(err)); exit(EXIT_FAILURE); } if ((err = set_swparams(handle, swparams)) < 0) { printf("Setting of swparams failed: %s\n", snd_strerror(err)); exit(EXIT_FAILURE); } if (verbose > 0) snd_pcm_dump(handle, output); samples =(signed short *) malloc((period_size * channels * snd_pcm_format_physical_width(format)) / 8); if (samples == NULL) { printf("No enough memory\n"); exit(EXIT_FAILURE); } areas = (snd_pcm_channel_area_t*)calloc(channels, sizeof(snd_pcm_channel_area_t)); if (areas == NULL) { printf("No enough memory\n"); exit(EXIT_FAILURE); } for (chn = 0; chn < channels; chn++) { areas[chn].addr = samples; areas[chn].first = chn * snd_pcm_format_physical_width(format); areas[chn].step = channels * snd_pcm_format_physical_width(format); } err = transfer_methods[method].transfer_loop(handle, samples, areas); if (err < 0) printf("Transfer failed: %s\n", snd_strerror(err)); free(areas); free(samples); snd_pcm_close(handle); #endif return 0; }
未完待續...
但願能幫到和我同樣的菜鳥...