2016-12-08 11:30:35 MINGZHNGLEI 阅读数 2273
  • FFmpeg音视频开发实战5 iOS/Android/windows/Linux

    本课程适合从事音视频,网络通讯开发的程序员。实战案例可用于 音视频处理,无人机,安防,直播等所有音视频领域。课程从Linux音视频采集,到TCP/IP UDP Socket服务器,客户端编程, 如何去定义网络通讯私有协议,x264,FFmpeg编解码,OpenGL ES渲染视频。OpenAL播放音频。到pcm实时转AAC,到H.264+AAC合成mp4, 整个流程,涵盖iOS,Android ,Mac 嵌入式Linux音视频相关绝大多数实用场景。以及Posix编程接口,C C++ Qt,FFmpeg跨平台开发,iOS,Android,Mac,linux,桌面软件都不再是障碍。让学员能够,融汇贯通掌握音视频领域相关知识,从事音视频相关职业,年薪轻松三四十万不是梦。 付费学员加入QQ群,可获得1~3年的专业解答,周六晚8:00 ~10:00 QQ群内部直播答疑, 以及就业指导,项目练习等服务.

    164964 人正在学习 去看看 陈超

android 发送语音功能和ios交互格式aac

看到标题大家应该都知道了,本文主要实现android发送语音功能
前面几篇博客写的webSocket,notifaction和service,当然了既然是即时通讯,怎么能少了语音聊天呢。

下面先简单说下语音功能的描述,在android中针对语音功能的是通过录音发送,做过的童鞋都知道在android中录音的格式是arm,这个在android设备通讯是没有问题的,但在ios设备不能播放,因为ios设备不支持arm格式的播放,最好查了好几篇博客,最好的解决方案是转换成AAC格式,至于为什么选择aac格式,在此不做过多的解释,如果想了解的朋友可以看http://blog.csdn.net/kingkong1024/article/details/14004885

下面就直接说下aac格式:
既然要发送语音,那就比需要对录音各个方面进行处理
大家看的最多应该就是微信语音功能了
首先对button按钮进行处理和封装

import android.content.Context;
import android.os.Environment;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;

import java.io.File;
import java.util.UUID;

/*****************************************************
 * author:      wz
 * email:       wangzhong0116@foxmail.com
 * version:     1.0
 * date:        2016/12/6 09:47
 * description: 控制录音Button
 * 1、重写onTouchEvent;(changeState方法、wantToCancel方法、reset方法);
 * 2、编写AudioDialogManage、并与该类AudioRecorderButton进行整合;
 * 3、编写AudioManage、并与该类AudioRecorderButton进行整合;
 *****************************************************/

public class AudioRecorderButton extends Button implements AudioManage.AudioStateListenter {


    /**
     * AudioRecorderButton的三个状态
     */
    private static final int STATE_NORMAL = 1;           //默认状态
    private static final int STATE_RECORDERING = 2;      //录音状态
    private static final int STATE_WANT_TO_CALCEL = 3;   //取消状态

    private int mCurState = STATE_NORMAL;    // 当前录音状态
    private boolean isRecordering = false;   // 是否已经开始录音
    private boolean mReady;    // 是否触发onLongClick

    private static final int DISTANCE_Y_CANCEL = 50;

    private AudioDialogManage audioDialogManage;

    private AudioManage mAudioManage;
    //    VoAACRecordManager manager;
    String mRecordFileName;

    private Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            int v = msg.what;
            System.out.println("=======" + v);
//            tv_voice.setText("voice:" + v);
        }
    };

    /**
     * 正常录音完成后的回调
     */
    public interface AudioFinishRecorderListenter {
        void onFinish(float seconds, String FilePath);
    }

    private AudioFinishRecorderListenter mListenter;

    public void setAudioFinishRecorderListenter(AudioFinishRecorderListenter listenter) {
        this.mListenter = listenter;
    }

    //构造方法
    public AudioRecorderButton(Context context) {
        super(context, null);
        // TODO Auto-generated constructor stub
    }

    public AudioRecorderButton(Context context, AttributeSet attrs) {
        super(context, attrs);

        audioDialogManage = new AudioDialogManage(getContext());

        final String dir = Environment.getExternalStorageDirectory()
                + "/VoiceRecorder/";                             // 此处需要判断是否有存储卡(外存)

        File file1 = new File(dir);   //
        if (!file1.exists()) {
            file1.mkdirs();
        }



        setOnLongClickListener(new OnLongClickListener() {

            @Override
            public boolean onLongClick(View v) {
                mRecordFileName = dir + UUID.randomUUID().toString() + ".aac";
                File file = new File(mRecordFileName);
                mAudioManage = AudioManage.getInstance(file);
                mAudioManage.setOnAudioStateListenter(AudioRecorderButton.this);
                mReady = true;
                // 真正显示应该在audio end prepared以后
                 mAudioManage.prepareAudio();
                isRecordering = true;
                mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);
                return false;
            }
        });
        // TODO Auto-generated constructor stub
    }

    /*
     * 复写onTouchEvent
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int action = event.getAction();   //获取当前Action

        int x = (int) event.getX();       //获取当前的坐标
        int y = (int) event.getY();

        switch (action) {

            case MotionEvent.ACTION_DOWN:
                changeState(STATE_RECORDERING);
                break;

            case MotionEvent.ACTION_MOVE:

                // 已经开始录音状态时,根据XY的坐标,判断是否想要取消
                if (isRecordering) {
                    if (wantToCancel(x, y)) {
                        changeState(STATE_WANT_TO_CALCEL);
                    } else {
                        changeState(STATE_RECORDERING);
                    }
                }
                break;

            case MotionEvent.ACTION_UP:
                if (!mReady) {   //没有触发onLongClick
                    reset();
                    return super.onTouchEvent(event);
                }

                if (!isRecordering || mTime < 0.7f) {  //录音时间过短
                    audioDialogManage.tooShort();
                    mAudioManage.cancel();
                    cancle();
                    mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1300);// 延迟,1.3秒以后关闭时间过短对话框                } else if (mCurState == STATE_RECORDERING) { //正常录制结束
                    audioDialogManage.dimissDialog();
                    // release
                    mAudioManage.release();
                    // callbackToAct
                    // 正常录制结束,回调录音时间和录音文件完整路径——在播放的时候需要使用
                    if (mListenter != null) {
                        mListenter.onFinish(mTime, mRecordFileName);
                    }

                } else if (mCurState == STATE_WANT_TO_CALCEL) {
                    // cancel
                    audioDialogManage.dimissDialog();
                    mAudioManage.cancel();
                    cancle();
                }

                reset();
                break;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 恢复状态以及一些标志位
     */
    private void reset() {
        isRecordering = false;
        mReady = false;                 //是否触发onLongClick
        mTime = 0;
        changeState(STATE_NORMAL);
    }

    private boolean wantToCancel(int x, int y) {
        // 判断手指的滑动是否超出范围
        if (x < 0 || x > getWidth()) {
            return true;
        }
        if (y < -DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL) {
            return true;
        }
        return false;
    }

    private void cancle() {
        if (mRecordFileName != null) {
            File file = new File(mRecordFileName);
            file.delete();    //删除录音文件
            mRecordFileName = null;
        }
    }

    /**
     * 改变Button的背景和文本、展示不同状态的录音提示对话框
     *
     * @param state
     */
    private void changeState(int state) {
        if (mCurState != state) {
            mCurState = state;
            switch (state) {
                case STATE_NORMAL:
                    setBackgroundResource(R.drawable.btn_recorder_normal);
                    setText(R.string.str_recorder_normal);
                    break;

                case STATE_RECORDERING:
                    setBackgroundResource(R.drawable.btn_recorder_recordering);
                    setText(R.string.str_recorder_recording);
                    if (isRecordering) {
                        // 更新Dialog.recording()
                        audioDialogManage.recording();
                    }
                    break;

                case STATE_WANT_TO_CALCEL:
                    setBackgroundResource(R.drawable.btn_recorder_recordering);
                    setText(R.string.str_recorder_want_cancel);
                    // 更新Dialog.wantCancel()
                    audioDialogManage.wantToCancel();
                    break;
            }
        }
    }


    @Override
    public void wellPrepared() {
        // TODO Auto-generated method stub
        mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);
    }

    private static final int MSG_AUDIO_PREPARED = 0x110;   //准备完全
    private static final int MSG_VOICE_CHANGE = 0x111;     //声音改变
    private static final int MSG_DIALOG_DIMISS = 0x112;    //销毁对话框

    /**
     * 接收子线程数据,并用此数据配合主线程更新UI
     * Handler运行在主线程(UI线程)中,它与子线程通过Message对象传递数据。
     * Handler接受子线程传过来的(子线程用sedMessage()方法传弟)Message对象,把这些消息放入主线程队列中,配合主线程进行更新UI     */
    private Handler mHandler = new Handler() {

        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
                case MSG_AUDIO_PREPARED:        //216:mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);
                    audioDialogManage.showRecorderingDialog();
                    isRecordering = true;
                    //已经在录制,同时开启一个获取音量、并且计时的线程
                    new Thread(mGetVoiceLevelRunnable).start();
                    break;

                case MSG_VOICE_CHANGE:          //265:mHandler.sendEmptyMessage(MSG_VOICE_CHANGE);
                    audioDialogManage.updateVoiceLevel(mAudioManage
                            .getVoiceLevel(7));
                    break;

                //这里在Handler里面处理DIALOG_DIMISS,是因为想让该对话框显示一段时间,延迟关闭,——详见125                case MSG_DIALOG_DIMISS:         //125:mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1300);
                    audioDialogManage.dimissDialog();
                    break;
            }
        }

        ;
    };

    private float mTime;  //开始录音时,计时;(在reset()中置空)
    /**
     * 获取音量大小的Runnable
     */
    private Runnable mGetVoiceLevelRunnable = new Runnable() {

        @Override
        public void run() {

            while (isRecordering) {

                try {
                    Thread.sleep(100);
                    mTime += 0.1f;
                    mHandler.sendEmptyMessage(MSG_VOICE_CHANGE);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

            }
        }
    };
}
下面录音的封装   格式aac格式
在此处需要导入
voaacencoder.jar
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.SystemClock;

import com.sinaapp.bashell.VoAACEncoder;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/*****************************************************
 * author:      wz
 * email:       wangzhong0116@foxmail.com
 * version:     1.0
 * date:        2016/12/6 09:52
 * description:
 *****************************************************/

public class AudioManage {

    private static AudioManage mInstance;

    private final String TAG = "VoAAVRecordManager";
    private AudioRecord mAudioRecord;
    private int SAMPLERATE = 8000;// 设置采样率
    private int bitRate = 16000;// 设置bit    private Handler mHandler;
    private long startTime;
    private long endTime;
    private boolean isRecodering;
    private int SPACE = 300;// 间隔取样时间


    private FileOutputStream fos;
    private int r;
    private int v;

    private File file;

    private AudioManage(File file) {
        this.file = file;
    }

    /**
     * 回调准备完毕     * @author wz
     *
     */
    public interface AudioStateListenter {
        void wellPrepared();    // prepared完毕
    }

    public AudioStateListenter mListenter;

    public void setOnAudioStateListenter(AudioStateListenter audioStateListenter) {
        mListenter = audioStateListenter;
    }

    /**
     * 使用单例实现 AudioManage
     * @param file
     * @return
     */
    //DialogManage主要管理DialogDialog主要依赖Context,而且此Context必须是ActivityContext    //如果DialogManage写成单例实现,将是Application级别的,将无法释放,容易造成内存泄露,甚至导致错误
    public static AudioManage getInstance(File file) {
        mInstance = new AudioManage(file);
//        if (mInstance == null) {
//            synchronized (AudioManage.class) {   // 同步
//                if (mInstance == null) {
//                    mInstance = new AudioManage(file);
//                }
//            }
//        }

        return mInstance;
    }

    /**
     * 准备录音
     */
    public void prepareAudio() {

        new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    fos = new FileOutputStream(file);
                } catch (FileNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                VoAACEncoder vo = new VoAACEncoder();
                int min = AudioRecord.getMinBufferSize(SAMPLERATE,// 得到一个需要的最小录音缓存大小,SAMPLERATE:采样率
                        AudioFormat.CHANNEL_IN_MONO,// 声道设置,单声道
                        AudioFormat.ENCODING_PCM_16BIT);// 编码制式
                vo.Init(SAMPLERATE, 16000, (short) 1, (short) 1);// 采样率:8000,bitRate:16000,声道数:1,编码:0.raw
                // 1.ADTS
                if (min < 2048) {
                    min = 2048;
                }
                byte[] temp = new byte[2048];
                mAudioRecord = new AudioRecord(
                        MediaRecorder.AudioSource.MIC,// 麦克风音源
                        SAMPLERATE, AudioFormat.CHANNEL_IN_DEFAULT,
                        AudioFormat.ENCODING_PCM_16BIT, min);
                mAudioRecord.startRecording();
                isRecodering = true;


                startTime = System.currentTimeMillis();
                updateMicStatus();
                while (isRecodering) {
                    r = mAudioRecord.read(temp, 0, min);
                    if (r > 0) {
                        v = 0;
                        for (int i = 0; i < temp.length; i++) {
                            v += temp[i] * temp[i];
                        }


                        byte[] ret = vo.Enc(temp);
                        try {
                            fos.write(ret);
                        } catch (IOException e) {
                            // TODO Auto-generated catch block
                            isRecodering = false;
                            e.printStackTrace();
                        }
                    }
                }
                mAudioRecord.stop();
                mAudioRecord.release();
                mAudioRecord = null;
                vo.Uninit();
                try {
                    fos.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }).start();
    }

    private void updateMicStatus() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                if (mHandler != null) {
                    while (isRecodering) {
                        int value = (int) (Math
                                .abs((int) (v / (float) r) / 100) >> 1);
                        // 得到即时声音的振幅(测试的是0-30之间)
                        System.out.println("=====振幅="+value);
                        mHandler.sendEmptyMessage(value);
                        SystemClock.sleep(SPACE);
                    }
                }
            }
        }).start();
    }



    /**
     * 获得音量等级——通过mMediaRecorder获得振幅,然后换算成声音Level
     * maxLevel最大为7     * mMediaRecorder.getMaxAmplitude() / 327680——1     * maxLevel * mMediaRecorder.getMaxAmplitude() / 32768 + 11——7     * @param maxLevel
     * @return
     */
    public int getVoiceLevel(int maxLevel) {
        if (isRecodering) {
            try {
                //mMediaRecorder.getMaxAmplitude()——获得最大振幅:1-32767
//                return maxLevel * mMediaRecorder.getMaxAmplitude() / 32768 + 1;

                int value = (int) (Math
                        .abs((int) (v / (float) r) / 100) >> 1);
                // 得到即时声音的振幅(测试的是0-30之间)
                System.out.println("=====振幅="+value);
                return maxLevel*value/30;
            } catch (Exception e) {
                // TODO Auto-generated catch block
                // e.printStackTrace();
            }
        }
        return 1;
    }

    /**
     * 释放资源
     */
    public void release() {
//        mMediaRecorder.stop();
//        mMediaRecorder.release();
//        mMediaRecorder = null;

        isRecodering=false;
    }

    /**
     * 取消(释放资源+删除文件)
     */
    public void cancel() {

        release();

//        if (mCurrentFilePath != null) {
//            File file = new File(mCurrentFilePath);
//            file.delete();    //删除录音文件
//            mCurrentFilePath = null;
//        }
    }

}
最好就是dialog的封装,并且对于音量振幅动画
大家可以看注释


import android.app.Dialog;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

/*****************************************************
 * author:      wz
 * email:       wangzhong0116@foxmail.com
 * version:     1.0
 * date:        2016/12/6 09:57
 * description: 管理录音提示对话框
 * 不同提示对话框显示原理:不同状态,显示部分View,隐藏部分View,然后居中显示
 *****************************************************/

public class AudioDialogManage {
    private Dialog mDialog;

    private ImageView mIcon;    //左侧图标
    private ImageView mVoice;   //声音展示

    private TextView mLabel;

    private Context mContext;

    public AudioDialogManage(Context context) {
        this.mContext = context;
    }

    /**
     * 默认的对话框的显示
     */
    public void showRecorderingDialog() {
        mDialog = new Dialog(mContext, R.style.Theme_AudioDialog);

        LayoutInflater inflater = LayoutInflater.from(mContext);
        View view = inflater.inflate(
                R.layout.voicenotes_recorder_dialog, null);
        mDialog.setContentView(view);

        mIcon = (ImageView) mDialog.findViewById(R.id.recorder_dialog_icon);
        mVoice = (ImageView) mDialog.findViewById(R.id.recorder_dialog_voice);
        mLabel = (TextView) mDialog.findViewById(R.id.recorder_dialog_label);

        mDialog.show();
    }

    //下面在显示各种对话框时,mDialog已经被构造,只需要控制ImageViewTextView的显示即可
    /**
     * 正在录音时,Dialog的显示
     */
    public void recording() {
        if (mDialog != null && mDialog.isShowing()) {
            mIcon.setVisibility(View.VISIBLE);
            mVoice.setVisibility(View.VISIBLE);
            mLabel.setVisibility(View.VISIBLE);

            mIcon.setImageResource(R.mipmap.recorder);
            mLabel.setText("手指滑动,取消录音");
        }
    }

    /**
     * 取消录音提示对话框
     */
    public void wantToCancel() {
        if (mDialog != null && mDialog.isShowing()) {
            mIcon.setVisibility(View.VISIBLE);
            mVoice.setVisibility(View.GONE);
            mLabel.setVisibility(View.VISIBLE);

            mIcon.setImageResource(R.mipmap.cancel);
            mLabel.setText("松开手指,取消录音");
        }
    }

    /**
     * 录音时间过短
     */
    public void tooShort() {
        if (mDialog != null && mDialog.isShowing()) {

            mIcon.setVisibility(View.VISIBLE);
            mVoice.setVisibility(View.GONE);
            mLabel.setVisibility(View.VISIBLE);

            mIcon.setImageResource(R.mipmap.voice_to_short);
            mLabel.setText("录音时间过短");
        }
    }

    /**
     * mDialog.dismiss();
     */
    public void dimissDialog() {
        if (mDialog != null && mDialog.isShowing()) {
            mDialog.dismiss();
            mDialog = null;
        }
    }

    /**
     * 通过Level更新Voice的图片:V1——V7
     * @param level
     */
    public void updateVoiceLevel(int level) {
        if (mDialog != null && mDialog.isShowing()) {

            int voiceResId=mContext.getResources().getIdentifier("v"+level, "mipmap", mContext.getPackageName());  //getIdentifier()获取应用包下指定资源的ID
            mVoice.setImageResource(voiceResId);
        }
    }
}

剩下的就是如何在activity中的应用和使用
主要调用封装button的回调
input_voice
                .setAudioFinishRecorderListenter(new AudioRecorderButton.AudioFinishRecorderListenter() {

                    @Override
                    public void onFinish(float seconds, String FilePath) {

                        System.out.println("======录音时间======="+seconds);
                        System.out.println("======录音acc地址===="+FilePath);
                        voiceSeconds= (int) seconds;
			//此处做的业务处理===发送语音
                        sendVoice(FilePath,1);
                    }
                });

最好贴上demohttp://download.csdn.net/detail/mingzhnglei/9705401
写的不好,欢迎大家留言,如果有需要,邮箱已经发了,就看各位怎么找了,可以发我的邮箱
2017-10-11 14:42:13 sy373466062 阅读数 2943
  • FFmpeg音视频开发实战5 iOS/Android/windows/Linux

    本课程适合从事音视频,网络通讯开发的程序员。实战案例可用于 音视频处理,无人机,安防,直播等所有音视频领域。课程从Linux音视频采集,到TCP/IP UDP Socket服务器,客户端编程, 如何去定义网络通讯私有协议,x264,FFmpeg编解码,OpenGL ES渲染视频。OpenAL播放音频。到pcm实时转AAC,到H.264+AAC合成mp4, 整个流程,涵盖iOS,Android ,Mac 嵌入式Linux音视频相关绝大多数实用场景。以及Posix编程接口,C C++ Qt,FFmpeg跨平台开发,iOS,Android,Mac,linux,桌面软件都不再是障碍。让学员能够,融汇贯通掌握音视频领域相关知识,从事音视频相关职业,年薪轻松三四十万不是梦。 付费学员加入QQ群,可获得1~3年的专业解答,周六晚8:00 ~10:00 QQ群内部直播答疑, 以及就业指导,项目练习等服务.

    164964 人正在学习 去看看 陈超

iOS/ OS X支持的可播放的Audio Format与Android支持的有些不一样。因此可能出现在Android设备上面使用MediaRecorder录制出来的音频文件在iOS上面不可用。当然也可能iOS上面录制的audio file在Android上面无法playback。

如果在iOS和Android上面开发类似微信这样的待遇语音交流的软件,那么就需要考虑iOS和Android上面录制出来的音频的跨系统的兼容性。

音频文件的基础知识

这方面涉及到下面几个方面:

  1. 采样率
  2. 比特率
  3. 声音通道
  4. 编码格式:指压缩方法
  5. 存储容器:即压缩后使用什么方式存储

对于这些可以看文章:How to convert between (most) audio formats in .NET


不同系统对音频的支持

iOS OS X支持的格式

找到Apple Developer的文档页面,或者google “iOS Audio Support Format”,可以看到apple的系统支持的格式如下。

File Format

Data Formats

AAC (.aac, .adts)

'aac '

AC3 (.ac3)

'ac-3'

AIFC (.aif, .aiff,.aifc)

BEI8, BEI16, BEI24, BEI32, BEF32, BEF64, 'ulaw', 'alaw', 'MAC3', 'MAC6', 'ima4' , 'QDMC', 'QDM2', 'Qclp', 'agsm'

AIFF (.aiff)

BEI8, BEI16, BEI24, BEI32

Apple Core Audio Format (.caf)

'.mp3', 'MAC3', 'MAC6', 'QDM2', 'QDMC', 'Qclp', 'Qclq', 'aac ', 'agsm', 'alac', 'alaw', 'drms', 'dvi ', 'ima4', 'lpc ', BEI8, BEI16, BEI24, BEI32, BEF32, BEF64, LEI16, LEI24, LEI32, LEF32, LEF64, 'ms\x00\x02', 'ms\x00\x11', 'ms\x001', 'ms\x00U', 'ms \x00', 'samr', 'ulaw'

MPEG Layer 3 (.mp3)

'.mp3'

MPEG 4 Audio (.mp4)

'aac '

MPEG 4 Audio (.m4a)

'aac ', alac'

NeXT/Sun Audio (.snd, .au)

BEI8, BEI16, BEI24, BEI32, BEF32, BEF64, 'ulaw'

Sound Designer II (.sd2)

BEI8, BEI16, BEI24, BEI32

WAVE (.wav)

LEUI8, LEI16, LEI24, LEI32, LEF32, LEF64, 'ulaw', 'alaw'

Key for linear PCM formats. For example, BEF32 = Big Endian linear PCM 32 bit floating point. 


Android支持的格式

参考Android开发者手册可以获取下面的内容

Format / Codec Encoder Decoder Details Supported File Type(s) / Container Formats
AAC LC Support for mono/stereo/5.0/5.1content with standard sampling rates from 8 to 48 kHz. • 3GPP (.3gp)
• MPEG-4 (.mp4, .m4a)
• ADTS raw AAC (.aac, decode in Android 3.1+, encode in Android 4.0+, ADIF not supported)
• MPEG-TS (.ts, not seekable, Android 3.0+)
HE-AACv1 (AAC+)
(Android 4.1+)
HE-AACv2 (enhanced AAC+)   Support for stereo/5.0/5.1content with standard sampling rates from 8 to 48 kHz.
AAC ELD (enhanced low delay AAC)
(Android 4.1+)

(Android 4.1+)
Support for mono/stereo contentwith standard sampling rates from 16 to 48 kHz
AMR-NB 4.75 to 12.2 kbps sampled @ 8kHz 3GPP (.3gp)
AMR-WB 9 rates from 6.60 kbit/s to 23.85 kbit/s sampled @ 16kHz 3GPP (.3gp)
FLAC  
(Android 3.1+)
Mono/Stereo (no multichannel). Sample rates up to 48 kHz (but up to 44.1kHz is recommended on devices with 44.1 kHz output, as the 48 to 44.1 kHzdownsampler does not include a low-pass filter). 16-bit recommended;no dither applied for 24-bit. FLAC (.flac) only
MIDI   MIDI Type 0 and 1. DLS Version 1 and 2. XMF and Mobile XMF. Support for ringtone formats RTTTL/RTX, OTA, and iMelody • Type 0 and 1 (.mid, .xmf, .mxmf)
• RTTTL/RTX (.rtttl, .rtx)
• OTA (.ota)
• iMelody (.imy)
MP3   Mono/Stereo 8-320Kbps constant (CBR) or variable bit-rate (VBR) MP3 (.mp3)
Opus  
(Android 5.0+)
  Matroska (.mkv)
PCM/WAVE
(Android 4.1+)
8- and 16-bit linear PCM (rates up to limit of hardware). Samplingrates for raw PCM recordings at 8000, 16000 and 44100 Hz. WAVE (.wav)
Vorbis     • Ogg (.ogg)
• Matroska (.mkv, Android 4.0+)

这里列举了Encoder和Decoder,分别对应到录音和播放。

因此我们的问题就变成了哪种/哪些Encoder是是在iOS和Android都支持的?

选择

我们可以看到AAC 编码 + MPEG-4 容器,是两个系统都支持的,那么可选择这个。

Android中可以使用MediaRecorder来完成声音的录制加编码处理,也可以使用底层的接口录制出PCM未压缩的格式,然后使用第三方库来进行编码。


在StackOverFlow中对此有回答(audio format for iOS and Android),经过个人验证,其实可以在Android端录制后取得后缀名为.m4a那么在iOS中也可以播放:



更进一步,在github找了个soundRecord apk源码,只需要做个简单的修改然后就可以用来验证:

$ git diff
diff --git a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java
index a8b36a1..e5047bb 100644
--- a/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java
+++ b/app/src/main/java/com/danielkim/soundrecorder/RecordingService.java
@@ -112,9 +112,9 @@ public class RecordingService extends Service {
             count++;
 
             mFileName = getString(R.string.default_file_name)
-                    + "_" + (mDatabase.getCount() + count) + ".mp4";
+                    + "_" + (mDatabase.getCount() + count) + ".m4a";
             mFilePath = Environment.getExternalStorageDirectory().getAbsolutePath();
-            mFilePath += "/SoundRecorder/" + mFileName;
+            mFilePath += "/TonyHo/" + mFileName;
 
             f = new File(mFilePath);
         }while (f.exists() && !f.isDirectory());


参考

1. NAudio

1.1  讲解:关于使用.Net来做Audio Encoder/Decoder的文章

1.2 NAudio Github

2. 格式转换与查看工具lameXPLameXP


2017-06-09 14:15:44 day_smile 阅读数 3576
  • FFmpeg音视频开发实战5 iOS/Android/windows/Linux

    本课程适合从事音视频,网络通讯开发的程序员。实战案例可用于 音视频处理,无人机,安防,直播等所有音视频领域。课程从Linux音视频采集,到TCP/IP UDP Socket服务器,客户端编程, 如何去定义网络通讯私有协议,x264,FFmpeg编解码,OpenGL ES渲染视频。OpenAL播放音频。到pcm实时转AAC,到H.264+AAC合成mp4, 整个流程,涵盖iOS,Android ,Mac 嵌入式Linux音视频相关绝大多数实用场景。以及Posix编程接口,C C++ Qt,FFmpeg跨平台开发,iOS,Android,Mac,linux,桌面软件都不再是障碍。让学员能够,融汇贯通掌握音视频领域相关知识,从事音视频相关职业,年薪轻松三四十万不是梦。 付费学员加入QQ群,可获得1~3年的专业解答,周六晚8:00 ~10:00 QQ群内部直播答疑, 以及就业指导,项目练习等服务.

    164964 人正在学习 去看看 陈超
Android录音格式传到ios播放不了问题:


首先是开发中android端使用录音模式为.amr的音频,但是经过转码后,ios端说在4.记得系统苹果就摒弃了.amr的语音录入方式,因为.amr的格式噪音比较大,被阉割掉了,但是要继续调查解决问题,ios端的支持什么wav,aac等格式,开始Android将音频转为wav传到ios端,但是不可用,最后Adnroid端录音格式改为AAC格式就好了:上代码     
 		   	   mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
                    mRecorder.setOutputFile(mVoiceData);
                    mRecorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS);
                    mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
也就是上面的代码中的
OutputFormat.AAC_ADTS  
AudioEncoder.AAC

上面的这个两个属性就是设置你的录音为AAC格式 ,然后将录音进行BAse64加密,传到ios进行解密,然后就可以了,解决了,
但是ios的音频发过来播放不了,最后发现android的手机的媒体音量没有开,然后播不出来了,草.......
最后换个手机发现了这个问题...
2016-12-18 17:52:36 lindir 阅读数 3151
  • FFmpeg音视频开发实战5 iOS/Android/windows/Linux

    本课程适合从事音视频,网络通讯开发的程序员。实战案例可用于 音视频处理,无人机,安防,直播等所有音视频领域。课程从Linux音视频采集,到TCP/IP UDP Socket服务器,客户端编程, 如何去定义网络通讯私有协议,x264,FFmpeg编解码,OpenGL ES渲染视频。OpenAL播放音频。到pcm实时转AAC,到H.264+AAC合成mp4, 整个流程,涵盖iOS,Android ,Mac 嵌入式Linux音视频相关绝大多数实用场景。以及Posix编程接口,C C++ Qt,FFmpeg跨平台开发,iOS,Android,Mac,linux,桌面软件都不再是障碍。让学员能够,融汇贯通掌握音视频领域相关知识,从事音视频相关职业,年薪轻松三四十万不是梦。 付费学员加入QQ群,可获得1~3年的专业解答,周六晚8:00 ~10:00 QQ群内部直播答疑, 以及就业指导,项目练习等服务.

    164964 人正在学习 去看看 陈超

最近再做一个功能,在ios上录制音频上传服务器,然后android和ios都可以播放,于是乎,先决定录制音频的格式

最先想到的是MP3,MP3在两者上都可以播放,但是很可惜,ios和andorid都不能直接录制mp3格式的音频文件。都需要转码

于是想到了AAC格式

ios可以直接录制AAC格式,并且ios和android都可以直接播放AAC格式的音频,而且AAC格式的压缩效率比MP3更高,更高的音质,更小的体积,绝对是最适合录制并且回复的。

swift下录制AAC的配置如下:

    let recordSettings:[String : AnyObject] = [
            AVFormatIDKey:             NSNumber(value: kAudioFormatMPEG4AAC),
            AVEncoderAudioQualityKey : NSNumber(value:AVAudioQuality.max.rawValue),
            AVNumberOfChannelsKey:     NSNumber(value:2),
            AVSampleRateKey :          NSNumber(value:11025.0),
            //AVEncoderBitRateKey:       NSNumber(value:64000),
            AVLinearPCMBitDepthKey:    NSNumber(value:16)
            ]

就能直接录制AAC格式的音频了




没有更多推荐了,返回首页