精华内容
下载资源
问答
  • Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压缩(如AMR、MP3等)并存成文件,而后者则更接近底层,...

    Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压缩(如AMR、MP3等)并存成文件,而后者则更接近底层,能够更加自由灵活地控制,可以得到原始的一帧帧PCM音频数据。


    如果想简单地做一个录音机,录制成音频文件,则推荐使用 MediaRecorder,而如果需要对音频做进一步的算法处理、或者采用第三方的编码库进行压缩、以及网络传输等应用,则建议使用 AudioRecord,其实 MediaRecorder 底层也是调用了 AudioRecord 与 Android Framework 层的 AudioFlinger 进行交互的。


    音频的开发,更广泛地应用不仅仅局限于本地录音,因此,我们需要重点掌握如何利用更加底层的 AudioRecord API 来采集音频数据(注意,使用它采集到的音频数据是原始的PCM格式,想压缩为mp3,aac等格式的话,还需要专门调用编码器进行编码)。


    1. AudioRecord 的工作流程


    首先,我们了解一下 AudioRecord 的工作流程:


    (1) 配置参数,初始化内部的音频缓冲区

    (2) 开始采集

    (3) 需要一个线程,不断地从 AudioRecord 的缓冲区将音频数据“读”出来,注意,这个过程一定要及时,否则就会出现“overrun”的错误,该错误在音频开发中比较常见,意味着应用层没有及时地“取走”音频数据,导致内部的音频缓冲区溢出。

    (4) 停止采集,释放资源


    2. AudioRecord 的参数配置


    wKioL1bhXMew-y-lAAFNssMMHH8488.png


    上面是 AudioRecord 的构造函数,我们可以发现,它主要是靠构造函数来配置采集参数的,下面我们来一一解释这些参数的含义(建议对照着我的上一篇文章来理解):


    (1) audioSource


    该参数指的是音频采集的输入源,可选的值以常量的形式定义在 MediaRecorder.AudioSource 类中,常用的值包括:DEFAULT(默认),VOICE_RECOGNITION(用于语音识别,等同于DEFAULT),MIC(由手机麦克风输入),VOICE_COMMUNICATION(用于VoIP应用)等等。


    (2) sampleRateInHz


    采样率,注意,目前44100Hz是唯一可以保证兼容所有Android手机的采样率。


    (3) channelConfig


    通道数的配置,可选的值以常量的形式定义在 AudioFormat 类中,常用的是 CHANNEL_IN_MONO(单通道),CHANNEL_IN_STEREO(双通道)


    (4) audioFormat


    这个参数是用来配置“数据位宽”的,可选的值也是以常量的形式定义在 AudioFormat 类中,常用的是 ENCODING_PCM_16BIT(16bit),ENCODING_PCM_8BIT(8bit),注意,前者是可以保证兼容所有Android手机的。


    (5) bufferSizeInBytes


    这个是最难理解又最重要的一个参数,它配置的是 AudioRecord 内部的音频缓冲区的大小,该缓冲区的值不能低于一帧“音频帧”(Frame)的大小,而前一篇文章介绍过,一帧音频帧的大小计算如下:


    int size = 采样率 x 位宽 x 采样时间 x 通道数


    采样时间一般取 2.5ms~120ms 之间,由厂商或者具体的应用决定,我们其实可以推断,每一帧的采样时间取得越短,产生的延时就应该会越小,当然,碎片化的数据也就会越多。


    在Android开发中,AudioRecord 类提供了一个帮助你确定这个 bufferSizeInBytes 的函数,原型如下:


    int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat);


    不同的厂商的底层实现是不一样的,但无外乎就是根据上面的计算公式得到一帧的大小,音频缓冲区的大小则必须是一帧大小的2~N倍,有兴趣的朋友可以继续深入源码探究探究。


    实际开发中,强烈建议由该函数计算出需要传入的 bufferSizeInBytes,而不是自己手动计算。


    3. 音频的采集线程


    当创建好了 AudioRecord 对象之后,就可以开始进行音频数据的采集了,通过下面两个函数控制采集的开始/停止:


    AudioRecord.startRecording();

    AudioRecord.stop();


    一旦开始采集,必须通过线程循环尽快取走音频,否则系统会出现 overrun,调用的读取数据的接口是:


    AudioRecord.read(byte[] audioData, int offsetInBytes, int sizeInBytes);


    4. 示例代码


    我将 AudioRecord 类的接口简单封装了一下,提供了一个 AudioCapturer 类,可以到我的Github下载:https://github.com/Jhuster/Android/blob/master/Audio/AudioCapturer.java


    这里也贴出来一份:


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    /*
     *  COPYRIGHT NOTICE  
     *  Copyright (C) 2016, Jhuster <lujun.hust@gmail.com>
     *  https://github.com/Jhuster/Android
     *   
     *  @license under the Apache License, Version 2.0 
     *
     *  @file    AudioCapturer.java
     *  
     *  @version 1.0     
     *  @author  Jhuster
     *  @date    2016/03/10    
     */
    import android.media.AudioFormat;
    import android.media.AudioRecord;
    import android.media.MediaRecorder;
    import android.util.Log;
     
    public class AudioCapturer {
     
        private static final String TAG = "AudioCapturer";
         
        private static final int DEFAULT_SOURCE = MediaRecorder.AudioSource.MIC;
        private static final int DEFAULT_SAMPLE_RATE = 44100;
        private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
        private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
     
        private AudioRecord mAudioRecord;
        private int mMinBufferSize = 0;
         
        private Thread mCaptureThread;  
        private boolean mIsCaptureStarted = false;
        private volatile boolean mIsLoopExit = false;
     
        private OnAudioFrameCapturedListener mAudioFrameCapturedListener;
     
        public interface OnAudioFrameCapturedListener {
            public void onAudioFrameCaptured(byte[] audioData);
        }  
     
        public boolean isCaptureStarted() {     
            return mIsCaptureStarted;
        }
     
        public void setOnAudioFrameCapturedListener(OnAudioFrameCapturedListener listener) {
            mAudioFrameCapturedListener = listener;
        }
     
        public boolean startCapture() {
            return startCapture(DEFAULT_SOURCE, DEFAULT_SAMPLE_RATE, DEFAULT_CHANNEL_CONFIG,
                DEFAULT_AUDIO_FORMAT);
        }
     
        public boolean startCapture(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) {
     
            if (mIsCaptureStarted) {
                Log.e(TAG, "Capture already started !");
                return false;
            }
         
            mMinBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat);
            if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) {
                Log.e(TAG, "Invalid parameter !");
                return false;
            }
            Log.d(TAG , "getMinBufferSize = "+mMinBufferSize+" bytes !");
             
            mAudioRecord = new AudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,mMinBufferSize);            
            if (mAudioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
                Log.e(TAG, "AudioRecord initialize fail !");
            return false;
            }      
     
            mAudioRecord.startRecording();
     
            mIsLoopExit = false;
            mCaptureThread = new Thread(new AudioCaptureRunnable());
            mCaptureThread.start();
     
            mIsCaptureStarted = true;
     
            Log.d(TAG, "Start audio capture success !");
     
            return true;
        }
     
        public void stopCapture() {
     
            if (!mIsCaptureStarted) {
                return;
            }
     
            mIsLoopExit = true;      
            try {
                mCaptureThread.interrupt();
                mCaptureThread.join(1000);
            
            catch (InterruptedException e) {    
                e.printStackTrace();
            }
     
            if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
                mAudioRecord.stop();                       
            }
     
            mAudioRecord.release();    
         
            mIsCaptureStarted = false;
            mAudioFrameCapturedListener = null;
     
            Log.d(TAG, "Stop audio capture success !");
        }
     
        private class AudioCaptureRunnable implements Runnable {      
         
            @Override
            public void run() {
     
                while (!mIsLoopExit) {
     
                    byte[] buffer = new byte[mMinBufferSize];
     
                    int ret = mAudioRecord.read(buffer, 0, mMinBufferSize);               
                    if (ret == AudioRecord.ERROR_INVALID_OPERATION) {
                        Log.e(TAG , "Error ERROR_INVALID_OPERATION");
                    
                    else if (ret == AudioRecord.ERROR_BAD_VALUE) {
                        Log.e(TAG , "Error ERROR_BAD_VALUE");
                    
                    else 
                        if (mAudioFrameCapturedListener != null) {
                            mAudioFrameCapturedListener.onAudioFrameCaptured(buffer);
                        }   
                        Log.d(TAG , "OK, Captured "+ret+" bytes !");
                    }                                                      
                }      
            }    
        }
    }


    使用前要注意,添加如下权限:


    <uses-permission android:name="android.permission.RECORD_AUDIO" />




    本文转自 Jhuster 51CTO博客,原文链接:http://blog.51cto.com/ticktick/1749719,如需转载请自行联系原作者


    展开全文
  • 由于源文件大小为99.3MB,所以采用分卷压缩的方式进行上传  《Google Android SDK开发范例大全(第3版)》在上一版的基础上,以Android手机应用程序开发(采用Android SDK 2.3.3)为主题,超过200多个范例全面且深度...
  • vc++ 应用源码包_1

    热门讨论 2012-09-15 14:22:12
    内含各种例子(vc下各种控件的使用方法、标题栏与菜单栏、工具栏与状态栏、图标与光标、程序窗口、程序控制、进程与线程、字符串、文件读写操作、文件与文件夹属性操作、文件与文件夹系统操作、系统控制操作、程序...
  • vc++ 应用源码包_2

    热门讨论 2012-09-15 14:27:40
    内含各种例子(vc下各种控件的使用方法、标题栏与菜单栏、工具栏与状态栏、图标与光标、程序窗口、程序控制、进程与线程、字符串、文件读写操作、文件与文件夹属性操作、文件与文件夹系统操作、系统控制操作、程序...
  • vc++ 应用源码包_6

    热门讨论 2012-09-15 14:59:46
    内含各种例子(vc下各种控件的使用方法、标题栏与菜单栏、工具栏与状态栏、图标与光标、程序窗口、程序控制、进程与线程、字符串、文件读写操作、文件与文件夹属性操作、文件与文件夹系统操作、系统控制操作、程序...
  • vc++ 应用源码包_5

    热门讨论 2012-09-15 14:45:16
    内含各种例子(vc下各种控件的使用方法、标题栏与菜单栏、工具栏与状态栏、图标与光标、程序窗口、程序控制、进程与线程、字符串、文件读写操作、文件与文件夹属性操作、文件与文件夹系统操作、系统控制操作、程序...
  • vc++ 应用源码包_4

    热门讨论 2012-09-15 14:38:35
    内含各种例子(vc下各种控件的使用方法、标题栏与菜单栏、工具栏与状态栏、图标与光标、程序窗口、程序控制、进程与线程、字符串、文件读写操作、文件与文件夹属性操作、文件与文件夹系统操作、系统控制操作、程序...
  • vc++ 应用源码包_3

    热门讨论 2012-09-15 14:33:15
    内含各种例子(vc下各种控件的使用方法、标题栏与菜单栏、工具栏与状态栏、图标与光标、程序窗口、程序控制、进程与线程、字符串、文件读写操作、文件与文件夹属性操作、文件与文件夹系统操作、系统控制操作、程序...
  • vc++ 开发实例源码包

    2014-12-16 11:25:17
    内含各种例子(vc下各种控件的使用方法、标题栏与菜单栏、工具栏与状态栏、图标与光标、程序窗口、程序控制、进程与线程、字符串、文件读写操作、文件与文件夹属性操作、文件与文件夹系统操作、系统控制操作、程序...
  •  cc实例160 用WinRar压缩和解压文件   cc实例161 判断文件是否被改动   第6章 操作系统与Windows相关程序   6.1 启动相关设置   cc实例162 进入WindowscXP前发出警告   cc实例163 实现关机.c重启...
  • 实例160 用WinRar压缩和解压文件 实例161 判断文件是否被改动 第6章 操作系统与Windows相关程序 6.1 启动相关设置 实例162 进入Windows XP前发出警告 实例163 实现关机、重启计算机 实例164 将程序设置成为...
  •  cc实例160 用WinRar压缩和解压文件   cc实例161 判断文件是否被改动   第6章 操作系统与Windows相关程序   6.1 启动相关设置   cc实例162 进入WindowscXP前发出警告   cc实例163 实现关机.c重启...
  •  cc实例160 用WinRar压缩和解压文件   cc实例161 判断文件是否被改动   第6章 操作系统与Windows相关程序   6.1 启动相关设置   cc实例162 进入WindowscXP前发出警告   cc实例163 实现关机.c重启...
  •  cc实例160 用WinRar压缩和解压文件   cc实例161 判断文件是否被改动   第6章 操作系统与Windows相关程序   6.1 启动相关设置   cc实例162 进入WindowscXP前发出警告   cc实例163 实现关机.c重启...
  •  cc实例160 用WinRar压缩和解压文件   cc实例161 判断文件是否被改动   第6章 操作系统与Windows相关程序   6.1 启动相关设置   cc实例162 进入WindowscXP前发出警告   cc实例163 实现关机.c重启...
  • 实例160 用WinRar压缩和解压文件 实例161 判断文件是否被改动 第6章 操作系统与Windows相关程序 6.1 启动相关设置 实例162 进入Windows XP前发出警告 实例163 实现关机、重启计算机 实例164 将程序设置成为...
  • 实例160 用WinRar压缩和解压文件 实例161 判断文件是否被改动 第6章 操作系统与Windows相关程序 6.1 启动相关设置 实例162 进入Windows XP前发出警告 实例163 实现关机、重启计算机 实例164 将程序设置成为...
  • 实例160 用WinRar压缩和解压文件 实例161 判断文件是否被改动 第6章 操作系统与Windows相关程序 6.1 启动相关设置 实例162 进入Windows XP前发出警告 实例163 实现关机、重启计算机 实例164 将程序设置成为...
  • 实例160 用WinRar压缩和解压文件 实例161 判断文件是否被改动 第6章 操作系统与Windows相关程序 6.1 启动相关设置 实例162 进入Windows XP前发出警告 实例163 实现关机、重启计算机 实例164 将程序设置成为...
  • 实例160 用WinRar压缩和解压文件 实例161 判断文件是否被改动 第6章 操作系统与Windows相关程序 6.1 启动相关设置 实例162 进入Windows XP前发出警告 实例163 实现关机、重启计算机 实例164 将程序设置成为...
  • 实例160 用WinRar压缩和解压文件 实例161 判断文件是否被改动 第6章 操作系统与Windows相关程序 6.1 启动相关设置 实例162 进入Windows XP前发出警告 实例163 实现关机、重启计算机 实例164 将程序设置成为...
  • 实例160 用WinRar压缩和解压文件 实例161 判断文件是否被改动 第6章 操作系统与Windows相关程序 6.1 启动相关设置 实例162 进入Windows XP前发出警告 实例163 实现关机、重启计算机 实例164 将程序设置成为...
  • 5.8 加密与解密 cc实例156 文件的加密与解密 cc实例157 文件夹加密 5.9 其他文件管理知识 cc实例158 文件分割器 cc实例159 帮助文件 cc实例160 用 WinRar压缩和解压文件 cc实例161 判断文件是否被...
  • 5.8 加密与解密 cc实例156 文件的加密与解密 cc实例157 文件夹加密 5.9 其他文件管理知识 cc实例158 文件分割器 cc实例159 帮助文件 cc实例160 用 WinRar压缩和解压文件 cc实例161 判断文件是否被...
  • 全书压缩打包成2部分,这是第1部分。 注:本系列图书的第I、II卷再版时均相应改名为《xxx开发实例大全》(基础卷)及(提高卷),但内容基本无变化,需要的童鞋可自由匹配查找。 内容简介  《Visual Basic开发实战...
  • 全书压缩打包成2部分,这是第2部分。 注:本系列图书的第I、II卷再版时均相应改名为《xxx开发实例大全》(基础卷)及(提高卷),但内容基本无变化,需要的童鞋可自由匹配查找。 内容简介  《Visual Basic开发实战...
  • 8.2.5 文件压缩(Jar、Zip) 245 8.3 SQLite数据库 249 8.3.1 SQLite数据库管理工具 249 8.3.2 SQLiteOpenHelper类与自动升级数据库 251 8.3.3 数据绑定与SimpleCursorAdapter类 252 8.3.4 操作SD卡上的...
  • 8.2.5 文件压缩(Jar、Zip) 245 8.3 SQLite数据库 249 8.3.1 SQLite数据库管理工具 249 8.3.2 SQLiteOpenHelper类与自动升级数据库 251 8.3.3 数据绑定与SimpleCursorAdapter类 252 8.3.4 操作SD卡上的...

空空如也

空空如也

1 2
收藏数 29
精华内容 11
关键字:

手机录音文件如何压缩