2016-12-08 11:30:35 MINGZHNGLEI 阅读数 2227
  • 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群内部直播答疑, 以及就业指导,项目练习等服务.

    164238 人正在学习 去看看 陈超

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-01-02 09:38:21 zhataijing5403 阅读数 422
  • 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群内部直播答疑, 以及就业指导,项目练习等服务.

    164238 人正在学习 去看看 陈超
我现在在做一个即时通讯软件,需要IOS、android互通。但是IOS那边录制的AAC音频我这边不能解码,包括windows的PC机也不可以解码。。但是iphone和macbook都可以播放,所以好像只有苹果的设备才可以播放。。
请问如何解决这个问题。
2016-12-18 17:52:36 lindir 阅读数 3089
  • 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群内部直播答疑, 以及就业指导,项目练习等服务.

    164238 人正在学习 去看看 陈超

最近再做一个功能,在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格式的音频了




2017-06-09 14:15:44 day_smile 阅读数 3389
  • 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群内部直播答疑, 以及就业指导,项目练习等服务.

    164238 人正在学习 去看看 陈超
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的手机的媒体音量没有开,然后播不出来了,草.......
最后换个手机发现了这个问题...
2012-10-01 12:28:35 windsoul85 阅读数 14701
  • 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群内部直播答疑, 以及就业指导,项目练习等服务.

    164238 人正在学习 去看看 陈超

由于工作需要,需要android与ios录音通用播放,ios不支持amr,android原生也不支持aac,最终由于进度情况,决定通用amr的方案,因为毕竟amr比较小巧。

那么ios就要 完成 录制后 把  编码转成  amr的,这样android 就可以正常播放了,测试可以

然后播放时要先把amr转成wav,就可以正常播放了


这里首先感谢这篇文章的作者,为本人开了路

http://hhuai.github.com/blog/2012/02/05/ios-and-andorid-voice/


而且开始用的好好的。可是ios6出现了,我日。。

opencore-amr for iOS

库要重新编译

然后就找到了这篇文章

http://blog.csdn.net/favormm/article/details/6772097

但一直有些小问题

最后还是找到了,源代码社区

http://sourceforge.net/mailarchive/forum.php?forum_name=opencore-amr-devel

下载了最新版opencore-amr-0.1.3.tar.gz代码

解压opencore-amr-0.1.3

在这找到了编译的脚本,改改库对应的路径和 armV7 的版本啊 什么的就可以了,我是放到 解压的目录下, ./build-iphone.sh 就可以了。

http://sourceforge.net/mailarchive/forum.php?thread_name=alpine.DEB.2.00.1110292052310.10431%40cone.martin.st&forum_name=opencore-amr-devel

下来来看了一下(由于对linux编译不熟悉,就尝试改了改)暂时测试是好用的  xcode4.5 貌似不支持 armv6了  所以就设置的armv7和armv7s


我把改的测试程序 传上来吧。有问题大家给我留言。我只是为使用重新编译了一下,也方便其他人使用和学习。有错误的地方还请指正,这里要感谢原作者和开源库。

有编译或使用的疑问欢迎大家探讨,

http://download.csdn.net/detail/windsoul85/4610958


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