美文网首页
Android音频PCM数据的采集和播放,读写wav文件

Android音频PCM数据的采集和播放,读写wav文件

作者: sparks_of_fire | 来源:发表于2020-04-23 17:18 被阅读0次

    2020年真的是非同一般的一年,全球疫情蔓延。早上坐出租时和司机师傅聊天,深深的感受到当前经济的艰难,受疫情影响,司机师傅最近接单特别少,语气中透露着无奈与担忧,也给自己提了个醒,一定要好好攒钱,以备不时之需,言归正传,伴随5G的来临,移动端音视频肯定会火,形势所迫,必须学习点硬货,未雨绸缪,因此在这里记录一下音视频开发的学习历程~~~         

    音频开发经常遇到的专业性词语

    (1)采样率

    "音频采样率“是指录音设备在一秒钟内对声音信号的采样次数,采样频率越高声音的还原就越真实越自然,常用的音频采样频率:8kHz,11.025kHz,22.05kHz,16kHz,37.8kHz,44.1kHz,48kHz,96kHz,192kHz等,在当今的主流采集卡上,采样频率一般共分为22.05kHz,44.1kHz,48kHz三个等级,22.05kHz只能达到FM广播的声音品质,44.1kHz则是理论上的CD音质界限,48kHz则更加精确一些。

    通俗理解:每秒录取声音的次数

    (2)量化精度(采样位数)

    ”采样位数“越大表示的值的范围也就越大

    ”采样位数“可以理解为采集卡处理声音的解析度,这个数值越大,解析度越高,录制和回放的声音就越真实,电脑中的声音文件是用数字0和1来表示的。连续的模拟信号按一定的采样频率经数码脉取样后,每一个离散的脉冲信号被以一定的量化精度量化成一串二进制编码流,这串编码的位数即为采样位数,也称为”量化精度“。

    常见的位数为:16bit和32bit

    通俗理解:每秒录取声音的精度,就像画面的分辨率,越高声音越真实

    (3)声道数

    声道数分别有:单声道的声道数为1个声道数;双声道的声道数为2个声道;立体声道的声道数默认为2个声道;立体声道(4声道)的声道数为4个声道。

    常见使用的是:单声道(MONO)和双声道(STEREO)

    (4)PCM编码与WAV格式

    PCM(Pulse Code Modulation---脉码调制录音)。所谓PCM录音就是将声音等模拟信号编程符号化的脉冲列,再予以记录,PCM信号是由【1】,【0】等符号构成的数字信号,而未经过任何编码和压缩处理,与模拟信号比,它不易传送系统的杂波及失真的影响,动态范围宽,可得到音质相当好的影响效果,也就是说,PCM就是没有压缩编码方式,PCM文件就是采用PCM这种没有压缩的编码方式编码的音频数据文件。

    PCM约定俗成了无损编码,因为PCM代表了数字音频中最佳的保真水准,并不意味着PCM就能够确保信号绝对保真,PCM也只能做到最大程度的无限接近。

    WAV为微软公司(Microsoft)开发的一种声音文件格式,它复合RIFF(Resource InterChange File Format)文件规范,用于保存windows平台的音频信息资源,被windows平台及其应用程序所广泛支持,该格式也支持MSADPCM,CCITT A LAW 等多种压缩运算法,支持多种音频数字,取样频率和声道,标准格式化的WAV文件和CD格式一样,也是44.1K取样频率,16位量化数字,因此在声音文件质量和CD相差无几!

    在windows平台下,基于PCM编码的WAV是被支持的最好的音频格式,所有音频软件都完美支持,由于本身可以达到较高的音质的要求,因此,WAV也是音乐编辑创作的首选格式,适合保存音乐素材,因此,基于PCM编码的WAV被作为了一种中介的格式,常常使用在其它编码的相互转换中,例如MP3转换WMA。

    通俗理解:PCM是一种没有压缩且无损的编码方式,WAV是微软开发的一种无损的音频文件格式,而WAV是通过PCM数据的基础上添加头部信息而生成的一种音频格式,当然也可以基于其它如ADPCM编码添加头部信息生成WAV。

    WAV文件格式

    在文件的前44字节放置标头(head),使播放器或编辑器能够简单掌握文件的基本信息,其内容以区块(chunk)为最小单位,每一区块长度为4字节。

    aa bb

    代码实现:

    // 音频数据的大小longtotalAudioLen=fileInputStream.getChannel().size();// wav总区块大小longtotalDataLen=totalAudioLen+36;// 声道数量intchannels;// 采样率longlongSampleRate;// 位元率longbyteRate=16*longSampleRate*channels/8;byte[]header=newbyte[44];// RIFF/WAVE headerheader[0]='R';header[1]='I';header[2]='F';header[3]='F';header[4]=(byte)(totalDataLen&0xff);header[5]=(byte)((totalDataLen>>8)&0xff);header[6]=(byte)((totalDataLen>>16)&0xff);header[7]=(byte)((totalDataLen>>24)&0xff);//WAVEheader[8]='W';header[9]='A';header[10]='V';header[11]='E';// 'fmt ' chunkheader[12]='f';header[13]='m';header[14]='t';header[15]=' ';// 4 bytes: size of 'fmt ' chunkheader[16]=16;header[17]=0;header[18]=0;header[19]=0;// format = 1header[20]=1;header[21]=0;header[22]=(byte)channels;header[23]=0;header[24]=(byte)(longSampleRate&0xff);header[25]=(byte)((longSampleRate>>8)&0xff);header[26]=(byte)((longSampleRate>>16)&0xff);header[27]=(byte)((longSampleRate>>24)&0xff);header[28]=(byte)(byteRate&0xff);header[29]=(byte)((byteRate>>8)&0xff);header[30]=(byte)((byteRate>>16)&0xff);header[31]=(byte)((byteRate>>24)&0xff);// block alignheader[32]=(byte)(2*16/8);header[33]=0;// bits per sampleheader[34]=16;header[35]=0;//dataheader[36]='d';header[37]='a';header[38]='t';header[39]='a';header[40]=(byte)(totalAudioLen&0xff);header[41]=(byte)((totalAudioLen>>8)&0xff);header[42]=(byte)((totalAudioLen>>16)&0xff);header[43]=(byte)((totalAudioLen>>24)&0xff);

    Android中MediaRecord和AudioRecord与MediaPlay和AudioTrack的介绍

    官方提供两种API用于音频开发,分别是MediaRecorder和AudioRecord用与音频的采集,MediaPalyer和AudioTrack用于音频的播放

    CC

    小知识点:

    1.在用MediaRecorder进行录制音视频时,最终还是会创建AudioRecord用来与AudioFliger进行交互。

    2.MediaPlay在framework层还是创建AudioTrack,把解码后的PCM数流传递给AudioTrack,AudioTrack再传递给AudioFlinger进行混音,然后才传递给硬件播放,所以MediaPlayer包含了AudioTRack.

    使用AudioRecord录制pcm音频

    /**

    * 采样率,现在能够保证在所有设备上使用的采样率是44100Hz, 但是其他的采样率(22050, 16000, 11025)在一些设备上也可以使用。

    */privatestaticfinalintSAMPLE_RATE_INHZ=44100;/**

    * 声道数。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保证在所有设备能够使用的。

    */privatestaticfinalintCHANNEL_CONFIG=AudioFormat.CHANNEL_IN_MONO;/**

    * 返回的音频数据的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.

    */privatestaticfinalintAUDIO_FORMAT=AudioFormat.ENCODING_PCM_16BIT;finalintminBufferSize=AudioRecord.getMinBufferSize(SAMPLE_RATE_INHZ,CHANNEL_CONFIG,AUDIO_FORMAT);audioRecord=newAudioRecord(MediaRecorder.AudioSource.MIC,SAMPLE_RATE_INHZ,CHANNEL_CONFIG,AUDIO_FORMAT,minBufferSize);finalbytedata[]=newbyte[minBufferSize];finalFilefile=newFile(getExternalFilesDir(Environment.DIRECTORY_MUSIC),"test.pcm");if(!file.mkdirs()){Log.e(TAG,"Directory not created");}if(file.exists()){file.delete();}audioRecord.startRecording();isRecording=true;newThread(newRunnable(){@Overridepublicvoidrun(){FileOutputStreamos=null;try{os=newFileOutputStream(file);}catch(FileNotFoundExceptione){e.printStackTrace();}if(null!=os){while(isRecording){intread=audioRecord.read(data,0,minBufferSize);// 如果读取音频数据没有出现错误,就将数据写入到文件if(AudioRecord.ERROR_INVALID_OPERATION!=read){try{os.write(data);}catch(IOExceptione){e.printStackTrace();}}}try{Log.i(TAG,"run: close file output stream !");os.close();}catch(IOExceptione){e.printStackTrace();}}}}).start();

    PCM转WAV

    只要加上wav头文件即可。

    使用AudioTrack播放pcm音频

    AudioTrack 类为Java程序实现了控制和播放简单的音频,它允许将PCM音频流传输到音频接收器进行播放,这是通过将音频数据推给AudioTrack对象实现的,可以使用write(byte[],int,int),write(short[],int,int)或write(float[],int,int,int)方法

    AudioTrack可以在两种模式下运行:static或streaming.

    在Streaming模式下,应用程序使用其中一种write()方法将连续的数据流写入AudioTrack,当数据从Java层传输到native层并排队等待播放时,它们会阻塞并返回,在播放音频数据块时,流模式非常有用,例如:

    由于声音播放的持续时间太长而不能装入内存,由于音频数据的特性(高采样率,每个样本的位数……)而不能装入内存,在先前排队的音频正在播放时接收或生成。

    在处理能够装入内存的短音时,应选择静态模式,并且需要尽可能以最小的延迟播放,因此,对于经常播放的UI和游戏声音而言,静态模式将是优选的,并且可能具有最小的开销。

    一旦创建,AudioTrack对象将初始化其关联的音频缓冲区,在构建过程中指定的这个缓冲区的大小决定了AudioTrack在耗尽数据之前可以播放多长时间。

    对于使用静态模式的AudioTrack,此大小是可以从中播放的最大声音大小。

    对于流模式,数据将以小于或等于总缓冲区大小的块形式写入音频接收器,AudioTrack不是final,因此允许使用子类,但不建议使用这种类型的子类。

    使用 AudioTrack 播放音频

    /** 播放,使用stream模   */

    privatevoidplayInModeStream(){

    /** SAMPLE_RATE_INHZ 对应pcm音频的采样率* channelConfig 对应pcm音频的声道* AUDIO_FORMAT 对应pcm音频的格式 * */

    intchannelConfig=AudioFormat.CHANNEL_OUT_MONO;finalintminBufferSize=AudioTrack.getMinBufferSize(SAMPLE_RATE_INHZ,channelConfig,AUDIO_FORMAT);audioTrack=newAudioTrack(newAudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build(),newAudioFormat.Builder().setSampleRate(SAMPLE_RATE_INHZ).setEncoding(AUDIO_FORMAT).setChannelMask(channelConfig).build(),minBufferSize,AudioTrack.MODE_STREAM,AudioManager.AUDIO_SESSION_ID_GENERATE);audioTrack.play();Filefile=newFile(getExternalFilesDir(Environment.DIRECTORY_MUSIC),"test.pcm");try{fileInputStream=newFileInputStream(file);newThread(newRunnable(){@Overridepublicvoidrun(){try{byte[]tempBuffer=newbyte[minBufferSize];while(fileInputStream.available()>0){intreadCount=fileInputStream.read(tempBuffer);if(readCount==AudioTrack.ERROR_INVALID_OPERATION||readCount==AudioTrack.ERROR_BAD_VALUE){continue;}if(readCount!=0&&readCount!=-1){audioTrack.write(tempBuffer,0,readCount);}}}catch(IOExceptione){e.printStackTrace();}}}).start();}catch(IOExceptione){e.printStackTrace();}}/**

        * 播放,使用static模式

        */privatevoidplayInModeStatic(){// static模式,需要将音频数据一次性write到AudioTrack的内部缓冲区newAsyncTask<Void,Void,Void>(){@OverrideprotectedVoiddoInBackground(Void...params){try{InputStreamin=getResources().openRawResource(R.raw.ding);try{ByteArrayOutputStreamout=newByteArrayOutputStream();for(intb;(b=in.read())!=-1;){out.write(b);}Log.d(TAG,"Got the data");audioData=out.toByteArray();}finally{in.close();}}catch(IOExceptione){Log.wtf(TAG,"Failed to read",e);}returnnull;}@OverrideprotectedvoidonPostExecute(Voidv){Log.i(TAG,"Creating track...audioData.length = "+audioData.length);// R.raw.ding铃声文件的相关属性为 22050Hz, 8-bit, MonoaudioTrack=newAudioTrack(newAudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build(),newAudioFormat.Builder().setSampleRate(22050).setEncoding(AudioFormat.ENCODING_PCM_8BIT).setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build(),audioData.length,AudioTrack.MODE_STATIC,AudioManager.AUDIO_SESSION_ID_GENERATE);Log.d(TAG,"Writing audio data...");audioTrack.write(audioData,0,audioData.length);Log.d(TAG,"Starting playback");audioTrack.play();Log.d(TAG,"Playing");}}.execute();}

    相关文章

      网友评论

          本文标题:Android音频PCM数据的采集和播放,读写wav文件

          本文链接:https://www.haomeiwen.com/subject/hgpoihtx.html