对于一个wave文件,如果需要播放,涉及到几个方面
1.对于wave文件的解析
2.通过解析wave文件,将得到的参数(主要是sampfrequency, bitsperSample,channel)通过alsa api设下去
3.正确找到data的起始点
4.play alsa
1.对于wave文件的解析,需要知道wave文件的格式
注意几点,标准的是44byte的头,但是有些情况下会有additional info, 占据2字节。头信息参见下图,也可以参考wave 文件解析
endian |
field name |
Size |
|
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,详见代码
/** @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 = , BITS_PER_SAMPLE_8 = , BITS_PER_SAMPLE_16 = , BITS_PER_SAMPLE_32 = }EBitsPerSample_t; typedef enum ENumOfChannels { NUM_OF_CHANNELS_1 = , NUM_OF_CHANNELS_2 = }ENumOfChannels_t; #if 0 /** PCM state */ typedef enum _snd_pcm_state { /** Open */ SND_PCM_STATE_OPEN = , /** 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; 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[]; uint8 ChunkSize[]; uint8 Format[]; uint8 SubChunk1ID[]; uint8 SubChunk1Size[]; uint8 AudioFormat[]; uint8 NumChannels[]; uint8 SampleRate[]; uint8 ByteRate[]; uint8 BlockAlign[]; uint8 BitsPerSample[]; uint8 CombineWaveFileExtra2Bytes[]; uint8 SubChunk2ID[]; uint8 SubChunk2Size[]; }Wave_Header_t; typedef struct Wave_Header_Size_Info { uint8 ChunkID[]; uint8 ChunkSize[]; uint8 Format[]; uint8 SubChunk1ID[]; uint8 SubChunk1Size[]; }Wave_Header_Size_Info_t; typedef struct Wave_Header_Audio_Info { uint8 AudioFormat[]; uint8 NumChannels[]; uint8 SampleRate[]; uint8 ByteRate[]; uint8 BlockAlign[]; uint8 BitsPerSample[]; }Wave_Header_Audio_Info_t; typedef struct Wave_Header_Additional_Info { uint8 AdditionalInfo_2Bytes[]; //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[]; uint8 SubChunk2Size[]; }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=; ;i<size;i++) { value=value+(arr[i]<<(*i)); } return value; } #if 0 //this is the return value ChunkID = "RIFF" ChunkSize = Format = "WAVE" fmt = "fmt " SubChunk1Size = AudioFormat = NumChannels = SampleRate = ByteRate = BlockAlign = BitsPerSample = SubChunk2ID = "data" SubChunk2Size = #endif //parse the wave file bool ParseWaveFile(const string wavepath,Alsa_Conf* palsaCfg,FILE** fp) { ; //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,,sizeof(Wave_Header_Size_Info_t)); ret=fread(&wav_size_info,,*fp); ) { cout<<"read error"<<endl; return false; } string ChunkID=""; ;i<;i++) { ChunkID+=wav_size_info.ChunkID[i]; } string riff="RIFF"; !=strcmp(ChunkID.c_str(),riff.c_str())) { cout<<"Invalid the fist Chunk id"<<endl; return false; } uint16 ChunkSize=HandleLittleEndian(wav_size_info.ChunkSize,); cout<<"The ChunSize is "<<ChunkSize<<endl; string Format=""; ;i<;i++) { Format+=wav_size_info.Format[i]; } !=strcmp(Format.c_str(),"WAVE")) { cout<<"Invalid the format"<<endl; return false; } string SubChunk1ID=""; ;i<;i++) { SubChunk1ID+=wav_size_info.SubChunk1ID[i]; } string fmt="fmt "; !=strcmp(SubChunk1ID.c_str(),fmt.c_str())) { cout<<"Invalid the SubChunk1ID "<<endl; return false; } uint16 SubChunk1Size=HandleLittleEndian(wav_size_info.SubChunk1Size,); && SubChunk1Size!=) { cout<<"Invalid the SubChunk1Size"<<endl; return false; } /*********************Audio info parse begin*************************/ Wave_Header_Audio_Info_t wav_audio_info; memset(&wav_audio_info,,sizeof(Wave_Header_Audio_Info_t)); ret=fread(&wav_audio_info,,*fp); ) { 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,); uint16 NumChannels =HandleLittleEndian(wav_audio_info.NumChannels,); uint16 SampleRate =HandleLittleEndian(wav_audio_info.SampleRate,); uint16 ByteRate =HandleLittleEndian(wav_audio_info.ByteRate,); uint16 BlockAlign =HandleLittleEndian(wav_audio_info.BlockAlign,); uint16 BitsPerSample=HandleLittleEndian(wav_audio_info.BitsPerSample,); palsaCfg->numOfChannels=(ENumOfChannels)NumChannels; palsaCfg->samplingFrequency=SampleRate; palsaCfg->bitsPerSample=(EBitsPerSample)BitsPerSample; /*********************Additional info parse begin if needed*************************/ ) { Wave_Header_Additional_Info_t wav_additional_info; memset(&wav_additional_info,,sizeof(Wave_Header_Additional_Info_t)); fread(&wav_additional_info,,*fp); cout<<"read wav_additional_info"<<endl; ) { cout<<"read error"<<endl; return false; } uint16 AdditionalInfo=HandleLittleEndian(wav_additional_info.AdditionalInfo_2Bytes,); cout<<"read AdditionalInfo value="<<AdditionalInfo<<endl; } /*********************Data info parse begin *************************/ Wave_Header_Data_Info_t wave_data_info; memset(&wave_data_info,,sizeof(Wave_Header_Data_Info_t)); fread(&wave_data_info,,*fp); ) { cout<<"read error"<<endl; return false; } string SubChunk2ID=""; ;i<;i++) { SubChunk2ID+=wave_data_info.SubChunk2ID[i]; } string fact="fact"; string data="data"; ==strcmp(SubChunk2ID.c_str(),fact.c_str())) { cout<<"SubChunk2ID fact"<<endl; } ==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,); cout<<"End Parse"<<endl; return true; } bool PlayWave(FILE** fp,snd_pcm_t** phandler,Alsa_Conf* palsaCfg) { ; bool ret=false; snd_pcm_uframes_t frames=palsaCfg->alsaPeriodFrame; ; //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<,sizeof(uint8)*audio_data_size); err=fread(buffer,,*fp); ) { cout<<"read error"<<endl; } if ( NULL != buffer ) { err = snd_pcm_writei(*phandler, buffer, frames); ) { cout<<"Fail to write the audio data to ALSA. Reason: "<<(snd_strerror(err)); // recover ALSA device err = snd_pcm_recover(*phandler, err, ); ) { 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 / ( * )); } delete[] buffer; buffer=NULL; return ret; } bool PrepareAlsaDevice(Alsa_Conf* palsaCfg,snd_pcm_t** phandler) { bool ret=false; bool success=true; ; snd_pcm_format_t format; snd_pcm_hw_params_t *hw_params = NULL; ; if(palsaCfg!=NULL) { // open ALSA device error=snd_pcm_open(phandler,palsaCfg->alsaDevice.c_str(),palsaCfg->streamType,palsaCfg->block? :SND_PCM_NONBLOCK); ) //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) ) { 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); ) { 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); ) { cout<<"[SG]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); ) { 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,); ) { 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); ) { 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); ) { cout<<"[SG]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); ) { cout<<"[SG]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); ) { 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); ) { cout<<"Fail to drop ALSA device. Reason: "<<(snd_strerror(snd_ret))<<endl; } } // close ALSA handler snd_ret = snd_pcm_close(*phandler); ) { 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 = ; //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 / ; // calculate the number of frame in one period m_palsaCfg->alsaBufferSize = m_palsaCfg->alsaPeriodFrame * ; //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 / ; // 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; ; }