精华内容
下载资源
问答
  • 节点-网络-pcm 使用 Node.js 的 PCM 音频流
  • Android 读取 PCM 音频流

    2021-06-12 06:19:31
    AudioTrack读取PCM import android.media.AudioFormat; import android.media.AudioTrack;... * 读取 PCM 音频流 * * @param filePath * @return */ static byte[] getPCMStream(String filePath) { List<By...

    AudioTrack 读取 PCM

    import android.media.AudioFormat;
    import android.media.AudioTrack;
    
    /**
     * 读取 PCM 音频流
     *
     * @param filePath
     * @return
     */
    static byte[] getPCMStream(String filePath) {
        List<Byte> stream = new ArrayList<>();
        int bufferSize = AudioTrack.getMinBufferSize(16000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(filePath);
            byte[] buffer = new byte[bufferSize];
            while (fis.read(buffer) != -1) {
                for (byte bf : buffer) {
                    stream.add(bf);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return Bytes.toArray(stream);
    }
    展开全文
  • PCM音频流的认识

    千次阅读 2018-05-17 16:09:53
    最近想写个软件,需要深度理解PCM音频流。查找资料其实不是很多,可能我想知道的别人不感兴趣,或者这东西被人不想让大家知道,没有太多人分享。我认识的也不够深刻,这就是个笔记,怕自己忘了。我是用WAV格式研究的...

    最近想写个软件,需要深度理解PCM音频流。查找资料其实不是很多,可能我想知道的别人不感兴趣,或者这东西被人不想让大家知道,没有太多人分享。

    我认识的也不够深刻,这就是个笔记,怕自己忘了。

    我是用WAV格式研究的PCM流。据说两个文件只差了一个文件头。

    有个数据属性是什么16Bit的,我不知道。

    一、认识曲线

    我使用Audacity(一个音频软件)截取了一个la音的振幅。


    这个是la 的振幅,不同的频率在一秒钟的变化不同(这好像是句废话),截取出的数据大小也就不同。

    我本身打算对一个振幅进行操作,现在发现不可以了,没准要对固定的采样数进行操作,我太水路太长!!!

    二、进制如何表示


    使用UltraEdit查看它的二进制文件


    前面的红色横框是文件头,中间的部分是数据,蓝框是尾部也没有用

    注意:红色的竖框表示的就是曲线的振幅。

    然后就是数据的意义。

    感谢网易云上的水滴课程,老师讲的16进制在这里用上了。(还要好好温习一下,都忘了)

    X轴上方的数据是00—7F;X轴下方的数据是FF—80;

    三、思考?

    竖红色框内的表示振幅,那么其余的数据呢???

    按照目前的理解可能已经能实现一个音频的改变,我还是想了解透彻了(嘿嘿)。

    想想好像很开心呢!

    下一篇文章我就要真正对文件操作了。


    展开全文
  • C#利用DirectSound可以实现把PCM音频流保存成WAV文件,进行播放。
  • 版权声明:本文为原创文章,未经允许不得转载 ...一录制和播放PCM音频流 录制 播放 测试 二录制和播放WAV音频文件 WAV简介 录制 播放 测试 三运行 四小结引用 联系方式 一、录制和播放PCM音频流1.录

    版权声明:本文为原创文章,未经允许不得转载

    博客地址:http://blog.csdn.net/kevindgk

    GitHub地址:https://github.com/KevinDGK/MyAudioDemo

    一、录制和播放PCM音频流

    1.录制

    • 抽取音频配置文件
    /**
     * Created by Kevin on 2016/10/24.
     * 音频配置文件
     */
    public class AudioConfig {
    
        public static final int sampleRateInHz = 44100;                            // 采样频率
        public static final int channelConfigIn = AudioFormat.CHANNEL_IN_STEREO;   // 双声道输入(立体声)
        public static final int audioFormat = AudioFormat.ENCODING_PCM_16BIT;      // 16bit
        public static final int audioSource = MediaRecorder.AudioSource.MIC;       // mic
    
        public static final int streamType = AudioManager.STREAM_MUSIC;            // 音频类型
        public static final int channelConfigOut = AudioFormat.CHANNEL_OUT_STEREO; // 双声道输出
        public static final int mode = AudioTrack.MODE_STREAM;                     // 输出模式
    
        public static final String DEFAULT_WAV_PATH = Environment.getExternalStorageDirectory() + "/test.wav";  // WAV音频文件保存路径
    }
    • 配置AudioRecord
        private int DEFAULT_SAMPLERATEINHZ = AudioConfig.sampleRateInHz;       // 采样频率
        private int DEFAULT_CHANNELCONFIG_IN = AudioConfig.channelConfigIn;    // 声道配置
        private int DEFAULT_AUDIOFORMAT = AudioConfig.audioFormat;             // 音频格式
        private int DEFAULT_AUDIOSOURCE = AudioConfig.audioSource;             // 音频来源

    注意:

    ① 采样频率为44.1KHz,位宽为16bit,录制出来的PCM音频一般称为无损音频,也是传统的CD格式;

    ② 声道配置为立体声(双声道),音频来源为麦克风;

    ③ 输出模式为音频流模式,表示不是直接播放音频文件,而是播放音频数据流;

    如果对于各个参数 不是很理解或者音频相关的基础较弱,可以先看前一篇的音频基础。

    • 音频录制的流程
    1. 获取最小的缓冲区大小
    2. 创建AudioRecord实例
    3. 创建录音线程
    4. 开始录音
    5. 读取语音信息
    6. 停止录音
    • 代码
        private static final String tag = "【AudioRecorder】";
    
        private int DEFAULT_SAMPLERATEINHZ = AudioConfig.sampleRateInHz;       // 采样频率
        private int DEFAULT_CHANNELCONFIG_IN = AudioConfig.channelConfigIn;    // 声道配置
        private int DEFAULT_AUDIOFORMAT = AudioConfig.audioFormat;             // 音频格式
        private int DEFAULT_AUDIOSOURCE = AudioConfig.audioSource;             // 音频来源
    
        private RecorderThread recorderThread;  // 录音线程
        private AudioRecord recorder;           // 录音对象
        private boolean isRunning;              // 录音线程是否运行
        private boolean isWorking;              // 录音线程是否工作(录音)
    
        private onRecorderListener recorderListener;
        private int recordBufferSize;
    
        public AudioRecorder(onRecorderListener recorderListener) {
            this.recorderListener = recorderListener;
            init();
        }
    
        /** 初始化 */
        private void init() {
    
            LogUtil.i(tag, "开始创建录音对象...");
    
            //1. 获取最小的缓冲区大小
            recordBufferSize = AudioRecord.getMinBufferSize(DEFAULT_SAMPLERATEINHZ,
                    DEFAULT_CHANNELCONFIG_IN, DEFAULT_AUDIOFORMAT);
            switch (recordBufferSize) {
                case AudioRecord.ERROR_BAD_VALUE:
                    LogUtil.i(tag, "无效的音频参数");
                    break;
                case AudioRecord.ERROR:
                    LogUtil.i(tag, "不能够查询音频输入的性能");
                    break;
                default:
                    LogUtil.i(tag, "AudioRecord的音频缓冲区的最小尺寸(与本机硬件有关):" + recordBufferSize);
                    break;
            }
    
            //2. 创建AudioRecord实例
            recorder = new AudioRecord(DEFAULT_AUDIOSOURCE, DEFAULT_SAMPLERATEINHZ,
                    DEFAULT_CHANNELCONFIG_IN, DEFAULT_AUDIOFORMAT, recordBufferSize * 4);
            switch (recorder.getState()) {
                case AudioRecord.STATE_INITIALIZED:
                    LogUtil.i(tag, "AudioTrack实例初始化成功!");
                    break;
                case AudioRecord.STATE_UNINITIALIZED:
                    LogUtil.i(tag, "AudioTrack实例初始化失败!");
                    break;
            }
    
            //3. 创建录音线程
            isRunning = true;
            isWorking = false;
            recorderThread = new RecorderThread();
        }
    
        /** 开始录音 */
        public void start() {
    
            if (isWorking) {
                CommUtil.Toast("正在录音中!");
                return;
            }
    
            try {
                //4. 开始录音
                recorderThread.start();
                recorder.startRecording();
                isWorking = true;
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    
        /** 停止录音 */
        public void stop() {
    
            if (!isWorking) {
                CommUtil.Toast("已经停止录音!");
            }
    
            //6. 停止录音
            try {
    
                recorder.stop();
                recorder.release();
                recorder = null;
    
                isWorking = false;
                recorderThread.interrupt();
                recorderThread.join(1000);  // 先停止录音线程,然后延时1s再停止播放器,防止程序崩溃
    
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /** 录音线程 */
        private class RecorderThread extends Thread {
    
            private byte[] recordData = new byte[recordBufferSize];   // 读取音频数据存放的数组
    
            @Override
            public void run() {
                super.run();
    
                while (isRunning) {
    
                    if (isWorking) {
    
                        //5. 读取语音信息
                        int readNumber = recorder.read(recordData, 0, recordData.length);
                        switch (readNumber) {
                            case AudioRecord.ERROR_INVALID_OPERATION:
                                LogUtil.i(tag, "读取语音信息...发现实例初始化失败!");
                                break;
                            case AudioRecord.ERROR_BAD_VALUE:
                                LogUtil.i(tag, "读取语音信息...发现参数无效!");
                                break;
                            default:
                                LogUtil.i(tag, "读取到的语音数据的长度:" + readNumber + " Shorts");
                                recorderListener.handleRecordData(recordData, 0, readNumber);
                                break;
                        }
                    }
                }
            }
        }
    
        /** 获取录音数据接口 */
        public interface onRecorderListener {
            void handleRecordData(byte[] recordData, int offset, int size);
        }
    • 注意事项

    ① 我们可以通过AudioRecord.getMinBufferSize()方法来获取最小的缓冲区大小recordBufferSize,和采样率、声道配置、音频格式和手机硬件有关,我们创建AudioRecord实例的时候需要传入缓冲区大小来创建一个音频缓冲区,用来存放MIC录制的音频数据,这个大小一般为recordBufferSize的倍数,比如2~4倍。如果这个缓冲区过小,当MIC写入速度大于读取速度的时候就会抛出异常。

    ② 通过AudioRecord获取的是原生的PCM音频数据,我们可以直接对原始数据操作,比如将其编码压缩,保存成其他的音频格式,也可以直接网络传输该数据流。但是,需要注意的就是,在这种情况下,码率 = 44.1K * 16 *2 =1411.2Kbps = 176.4KBps,也就是说,每秒占用的带宽就会达到176.4KB,相当的耗费流量。如果要用于局域网语音通信或者互联网语音电话,我们还需要进行修改配置和进行编解码,详见下一篇局域网语音通信和音频压缩。

    ③ 设置录音数据接口,用来实时从缓冲区读取音频数据。

    2.播放

    • 配置AudioTrack
        private int DEFAULT_SAMPLERATEINHZ = AudioConfig.sampleRateInHz;        // 采样频率
        private int DEFAULT_AUDIOFORMAT = AudioConfig.audioFormat;              // 数据格式
        private int DEFAULT_STREAMTYPE = AudioConfig.streamType;                // 音频类型
        private int DEFAULT_CHANNELCONFIG_OUT = AudioConfig.channelConfigOut;   // 声道配置
        private int DEFAULT_MODE = AudioConfig.mode;                            // 输出模式
    • 音频播放的流程
    1. 获取最小缓冲区大小
    2. 创建AudioTrack实例
    3. 设置开始工作
    4. 写入数据
    5. 播放音频数据
    6. 停止播放
    • 代码
        private int DEFAULT_SAMPLERATEINHZ = AudioConfig.sampleRateInHz;        // 采样频率
        private int DEFAULT_AUDIOFORMAT = AudioConfig.audioFormat;              // 数据格式
        private int DEFAULT_STREAMTYPE = AudioConfig.streamType;                // 音频类型
        private int DEFAULT_CHANNELCONFIG_OUT = AudioConfig.channelConfigOut;   // 声道配置
        private int DEFAULT_MODE = AudioConfig.mode;                            // 输出模式
    
        private AudioTrack player;      // 播放器实例
        private boolean isWorking;      // 是否正在工作
        private int playerBufferSize;   // 缓冲区大小
    
        public AudioPlayer() {
            init();
        }
    
        /** 初始化 */
        private void init() {
    
            //1. 获取最小缓冲区大小
            playerBufferSize = AudioTrack.getMinBufferSize(DEFAULT_SAMPLERATEINHZ,
                    DEFAULT_CHANNELCONFIG_OUT, DEFAULT_AUDIOFORMAT);
            switch (playerBufferSize) {
                case AudioTrack.ERROR_BAD_VALUE:
                    LogUtil.i(tag, "无效的音频参数");
                    break;
                case AudioTrack.ERROR:
                    LogUtil.i(tag, "不能够查询音频输出的性能");
                    break;
                default:
                    LogUtil.i(tag, "AudioTrack的音频缓冲区的最小尺寸(与本机硬件有关):" + playerBufferSize);
                    break;
            }
    
            //2. 创建AudioTrack实例
            player = new AudioTrack(DEFAULT_STREAMTYPE, DEFAULT_SAMPLERATEINHZ, DEFAULT_CHANNELCONFIG_OUT,
                    DEFAULT_AUDIOFORMAT, playerBufferSize * 4, DEFAULT_MODE);
            switch (player.getState()) {
                case AudioTrack.STATE_INITIALIZED:
                    LogUtil.i(tag, "AudioTrack实例初始化成功!");
                    break;
                case AudioTrack.STATE_UNINITIALIZED:
                    LogUtil.i(tag, "AudioTrack实例初始化失败!");
                    break;
                case AudioTrack.STATE_NO_STATIC_DATA:
                    LogUtil.i(tag, "AudioTrack实例初始化成功,目前没有静态数据输入!");
                    break;
            }
            LogUtil.i(tag, "当前AudioTrack实例的播放状态:" + player.getPlayState());
    
            //3. 设置开始工作
            isWorking = true;
        }
    
        /** 播放语音数据 */
        public void play(byte[] audioData,int offset, int size) {
    
            if (!isWorking) {
                CommUtil.Toast("AudioTrack is not working!");
            }
    
            try {
                //4. 写入数据
                int write = player.write(audioData, offset, size);
    
                if (write < 0) {
                    LogUtil.i(tag, "write失败");
                    switch (write) {
                        case AudioTrack.ERROR_INVALID_OPERATION:    // -3
                            LogUtil.i(tag, "AudioTrack实例初始化失败!");
                            break;
                        case AudioTrack.ERROR_BAD_VALUE:            // -2
                            LogUtil.i(tag, "无效的音频参数");
                            break;
                        case AudioTrack.ERROR:                      // -1
                            LogUtil.i(tag, "通用操作失败");
                            break;
                    }
                } else {
                    LogUtil.i(tag, "成功写入数据:" + size + " Shorts");
                }
    
                //5. 播放音频数据
                player.play();
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    
        /** 停止播放语音数据 */
        public void stop() {
    
            if (!isWorking) {
                CommUtil.Toast("已经停止播放!");
                return;
            }
    
            //6. 停止播放
            try {
                player.stop();
                player.release();
                player = null;
    
                isWorking = false;
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public int getMinBufferSize() {
            return playerBufferSize;
        }
    • 注意事项

    ① 缓冲区大小也是为获取的最小尺寸的2~4倍;

    ② 录音线程必须在子线程,语音播放的线程可以在子线程也可以在主线程。在这里,为了比较直观,就直接放在了主线程,但是实际开发中,往往需要放到子线程中。

    3.测试

    public class PCMTester implements BaseTester, AudioRecorder.onRecorderListener {
    
        private static final String tag = "【PCMTester】";
    
        private AudioRecorder recorder;
        private AudioPlayer player;
    
        @Override
        public void start() {
    
            player = new AudioPlayer();         // 创建播放器对象
            recorder = new AudioRecorder(this); // 创建录音对象
            recorder.start();                   // 开始录音
        }
    
        @Override
        public void stop() {
    
            recorder.stop();    // 停止录音
            player.stop();      // 停止播放
        }
    
        @Override
        public void handleRecordData(byte[] recordData, int offset, int size) {
    
            // 将录音捕捉的音频数据写入到播放器中播放
            if (player != null) {
                player.play(recordData, offset, size);
            }
        }
    }

    二、录制和播放WAV音频文件

    WAV简介

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

    • 文件扩展名:”.wav”
    • 文件格式:文件头+音频数据流
    • 数据流格式:PCM或压缩型

    详细的信息和文件头参数可以参考百度百科,或者自行搜索。

    1.录制

    • 流程
    1.创建并打开WAV格式的音频文件
    2.写入WAV音频文件头信息
    3.开始录制,同时将音频数据写入文件
    4.停止录制,然后关闭文件

    其中,WAV格式的数据流可以为PCM音频流,所以我们直接用上面的录制和播放PCM音频的代码,我们主要的工作就是根据格式添加头信息,小编懒得写,直接从大神的代码里Copy了一份,底部有相关博客链接。

    • WavFileWriter文件写入类代码如下
    public class WavFileWriter {
    
        private String mFilepath;   // 文件保存路径
        private int mDataSize = 0;  // 数据大小
        private DataOutputStream mDataOutputStream; // 包装数据流
    
        /**
         * 创建并打开WAV格式的音频文件,并写入头信息
         * @param filepath  文件路径
         * @param sampleRateInHz    采样频率
         * @param bitsPerSample     每个采样点的bit位数
         * @param channels          声道的数量
         * @return
         * @throws IOException
         */
        public boolean openFile(String filepath, int sampleRateInHz, int bitsPerSample, int channels) throws IOException {
            if (mDataOutputStream != null) {
                closeFile();
            }
            mFilepath = filepath;
            mDataSize = 0;
            mDataOutputStream = new DataOutputStream(new FileOutputStream(filepath));
            return writeHeader(sampleRateInHz, bitsPerSample, channels);
        }
    
        /** 关闭文件 */
        public boolean closeFile() throws IOException {
            boolean ret = true;
            if (mDataOutputStream != null) {
                ret = writeDataSize();
                mDataOutputStream.close();
                mDataOutputStream = null;
            }
            return ret;
        }
    
        /** 写入音频信息 */
        public boolean writeData(byte[] buffer, int offset, int count) {
    
            if (mDataOutputStream == null) {
                return false;
            }
    
            try {
                mDataOutputStream.write(buffer, offset, count);
                mDataSize += count;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
    
            return true;
        }
    
        /** 写入头信息 */
        private boolean writeHeader(int sampleRateInHz, int bitsPerSample, int channels) {
    
            if (mDataOutputStream == null) {
                return false;
            }
    
            WavFileHeader header = new WavFileHeader(sampleRateInHz, bitsPerSample, channels);
    
            try {
                mDataOutputStream.writeBytes(header.mChunkID);
                mDataOutputStream.write(intToByteArray(header.mChunkSize), 0, 4);
                mDataOutputStream.writeBytes(header.mFormat);
                mDataOutputStream.writeBytes(header.mSubChunk1ID);
                mDataOutputStream.write(intToByteArray(header.mSubChunk1Size), 0, 4);
                mDataOutputStream.write(shortToByteArray(header.mAudioFormat), 0, 2);
                mDataOutputStream.write(shortToByteArray(header.mNumChannel), 0, 2);
                mDataOutputStream.write(intToByteArray(header.mSampleRate), 0, 4);
                mDataOutputStream.write(intToByteArray(header.mByteRate), 0, 4);
                mDataOutputStream.write(shortToByteArray(header.mBlockAlign), 0, 2);
                mDataOutputStream.write(shortToByteArray(header.mBitsPerSample), 0, 2);
                mDataOutputStream.writeBytes(header.mSubChunk2ID);
                mDataOutputStream.write(intToByteArray(header.mSubChunk2Size), 0, 4);
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
    
            return true;
        }
    
        /** 写入数据大小 */
        private boolean writeDataSize() {
    
            if (mDataOutputStream == null) {
                return false;
            }
    
            try {
                RandomAccessFile wavFile = new RandomAccessFile(mFilepath, "rw");
                wavFile.seek(WavFileHeader.WAV_CHUNKSIZE_OFFSET);
                wavFile.write(intToByteArray(mDataSize + WavFileHeader.WAV_CHUNKSIZE_EXCLUDE_DATA), 0, 4);
                wavFile.seek(WavFileHeader.WAV_SUB_CHUNKSIZE2_OFFSET);
                wavFile.write(intToByteArray(mDataSize), 0, 4);
                wavFile.close();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                return false;
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }
    
            return true;
        }
    
        /** int转byte */
        private static byte[] intToByteArray(int data) {
            return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(data).array();
        }
    
        /** short转byte */
        private static byte[] shortToByteArray(short data) {
            return ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(data).array();
        }
    }

    文件头信息详见代码,链接在顶部。

    2.播放

    使用AudioTrack回放音频数据的代码同上,我们主要做的就是读取头信息,具体的详见代码。

    3.测试

    public class PCMTester implements BaseTester, AudioRecorder.onRecorderListener {
    
        private static final String tag = "【PCMTester】";
    
        private AudioRecorder recorder;
        private AudioPlayer player;
    
        @Override
        public void start() {
    
            player = new AudioPlayer();         // 创建播放器对象
            recorder = new AudioRecorder(this); // 创建录音对象
            recorder.start();                   // 开始录音
        }
    
        @Override
        public void stop() {
    
            recorder.stop();    // 停止录音
            player.stop();      // 停止播放
        }
    
        @Override
        public void handleRecordData(byte[] recordData, int offset, int size) {
    
            // 将录音捕捉的音频数据写入到播放器中播放
            if (player != null) {
                player.play(recordData, offset, size);
            }
        }
    }

    三、运行

    四、小结

    在实际开发中,除视音频应用外,我们使用到音频的播放和录制的场景并不太多,尤其是大部分我们都会直接使用MediaRecorder\MediaPlayer\SoundPool等进行音频的录制和播放,因为大多不需要直接接触到原始的音频数据流,直接录制和播放音频文件,比较方便。但是如果想要实现定制,比如音频的编码和压缩,就需要使用AudioRecord和AudioTrack进行编码,比较更加接近于底层。具体使用哪种方式,视需求而定。

    本文介绍的方法都是比较原生的使用,如果想要实现实时传输音频数据流,可以参考下一篇博客,在局域网中实现了语音数据的传输以及音频编码相关的知识。

    引用

    1.如何存储和解析wav文件 - 卢俊

    2.AudioTrack分析

    联系方式

    邮箱:815852777@qq.com

    微信:

    展开全文
  • WAV PCM 音频流调速算法,从开源的SoundTouch例子中提取。 压缩包里包含有VB版、Delphi版、易语言等版本。
  • 基于C语言实现PCM音频流或音频文件重采样(48K到16K) 由于云厂商SDK需要的音频采样率是48K的,而SFU回调上来的流是48K的,所以我们还需要对PCM音频数据进行重采样处理。 ​ 转换的原理比较简单,从48KHz降到16KHz,...

    基于C语言实现PCM音频流或音频文件重采样(48K到16K)

    由于云厂商SDK需要的音频采样率是16K的,而SFU回调上来的流是48K的,所以我们还需要对PCM音频数据进行重采样处理。

    ​ 转换的原理比较简单,从48KHz降到16KHz,降了3倍,也就是说在同一时间的单位区间内,48KHz采样了3个点,而16KHz只采样了1个点,即从48KHz的 PCM流中每读取3个数据,就要根据这3个数据去推算得到1个数据,而这个数据对应的就是16KHz PCM流中的一个数据。

    重采样

    我们有很多种方法可以由3个数据得到1个数据,如取平均值,取采样点等方法:

    (1). 取平均值:

    取3个点,相加后求平均值。这样的效果并不好,转换后声音会有杂音。这是因为转换成16kHz的采样率时,根据奈奎斯特采样定律,当采样频率fs.max大于信号中最高频率fmax的2倍时(fs.max>2fmax),,那么根据这些抽样值就能完全恢复原信号,所以转换成16kHz的采样率时,不能有8K以上的频率。而对3个点求平均值,是会有概率存在着8khz以上的频段,故效果并不好。

    (2). 取特征点:

    取3个采样点中固定位置的某个点,如固定取3个点中的第一个点,然后合并成新的数据流。这样的效果,实测比较清晰,虽然有概率会丢失部分声音细节,但是整体还是能够满足语音识别。

    综上所述,我们采取第一个特征点的方法。

    具体实现如下

    C语言实现

    处理pcm流:

    /** PCM frame. */
    typedef struct {
        uint8_t        *buf;
        int             len;
        int             sample_rate;
        int             sample_depth;
    } pcm_frame_t;
    
    void pcm_resample_16k(const pcm_frame_t *frame_in, pcm_frame_t *frame_out)
    {
        int i;
        int magnification = frame_in->sample_rate / 16000;
    
        frame_out->buf = frame_in->buf;
        frame_out->len = frame_in->len / magnification;
        frame_out->sample_rate = 16000;
        frame_out->sample_depth = frame_in->sample_depth;
    
    
        for (i = 0; i < frame_out->len; i++) {
            frame_out->buf[i] = frame_in->buf[i*magnification]; /* 取第一个特征值 */
        }
    
    
        /* 如果按照16bit来 */
    //     for (i = 0; i < len / 3; i++) {
    //         if (0 == (i % 2)) {
    //             buf[i] = buf[i*3+2];
    //         } else {
    //             buf[i] = buf[i*3];
    //         }
    //     }
    }
    

    处理pcm文件:

    void pcm_resample(void)
    {
        short read_buf = 0;
        int size = 0;
        int cnt = 0;
    
        FILE *fp = fopen("in.pcm", "rb+");
        FILE *fp_out = fopen("out.pcm", "wb+");
    
        while (!feof(fp)) {
            size = fread(&read_buf, 2, 1, fp); // 16bit,所以需要一次读两个字节
            if (size > 0) {
                cnt++;
                if (cnt == 3) { //每采集到3个点
                    cnt = 0; // 重置计数
                    fwrite(&read_buf, 2, 1, fp_out); //写入数据
                }
            }
        }
    
        fclose(fp);
        fclose(fp_out);
    }
    
    展开全文
  • iOS-EchoCancellation 使用AudioUnit进行声音采集,同时实现回声消除。采用AudioQueue对采集的pcm音频流进行播放
  • BREW中使用ISource和IMedia接口播放音频流演示程序,可以在模拟器中运行。注意退出时还有内存泄漏。
  • 音频直播服务是叫做 LANmic 无线话筒 的安卓程序。 访问http://192.168.1.8:8080 就能播放了。可以网页播放,vlc,ffmpeg, 那么我sdl能不能播放呢?LANmic 提供了wav编码,可以直接pcm播放。 经过搜索,发现搜...
  • 根据近期项目中应用需要,需要将rtp协议承载的amr(8kHZ)媒体流,转换成pcm格式音频流并以udp协议发送出去。ffmpeg强大的媒体处理功能,再次得到了淋漓尽致的体现,不多说了,直接上代码,记录一下 #include <...
  • 最新PCM抓取方法: 1、连接手机,执行 adb install MediaTest.apk 2、执行initialized.cmd脚本 3、手机上打开apk内的【数据开关】,1代表开始抓取,0代表结束抓取 4、复现需抓取音频的场景 5、关闭apk内的数据开关 6...
  • 需求:IOS播放PCM音频流数据

    千次阅读 2015-01-24 22:49:01
    需求:播放PCM流数据,即从sock接收数据,解析出PCM数据后,进行播放。 网上给到资料大多是播放音频文件,而我想要的是播放网络数据,于是参考写下如下PCM播放器。 PCMPlayer.h文件 #import #import #...
  • AVCodec *m_DecodeCodec; AVCodecContext *m_DecodeC; int CMyAudio::InitDecode() ... m_DecodeCodec = avcodec_find_decoder(CODEC_ID_ADPCM_IMA_WAV);...aac的音频流 这样 解码成 pcm音频流 行吗
  • SDL播放PCM音频

    热门讨论 2013-01-11 11:13:49
    里面含有一个能播放PCM音频流的源代码文件和一个audio.pcm用于测试。加入到工程中,需要自行配置SDL的头文件位置和SDL.lib,SDLmain.lib。
  • C++ 播放音频流(PCM裸流)

    热门讨论 2014-06-11 01:11:33
    这份代码是打开PCM文件并截取一段数据然后播放的,可以轻松的经过加一条线程的方式改成网络传输的形式。
  • 这份代码实现打开PCM文件并用双缓存机制进行播放的,可在VS2013下直接编译运行,其中包含一个PCM文件。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,998
精华内容 6,399
关键字:

pcm音频流