当前位置: 代码迷 >> Android >> Android 及时音视频解决方案2——腾讯云
  详细解决方案

Android 及时音视频解决方案2——腾讯云

热度:86   发布时间:2016-04-27 22:47:28.0
Android 即时音视频解决方案2——腾讯云

上一篇文章介绍了环信的解决方案,见Android 即时音视频解决方案1——环信,这篇文章,介绍一下更加靠谱,也就是腾讯云的解决方案,毕竟腾讯是是这方面的头头,比较靠谱。当然,集成腾讯云比集成环信稍微复杂那么一点,需要有一点点的耐心。

官方地址音视频云通信 AVC

SDK下载AV Andriod1.3

文档地址音视频云通讯

先讲讲腾讯云的原理,使用腾讯云的时候,要有一个账号体系,这个账号体系比较灵活,可以使用独立模式也可以只用第三方账号体系,这里使用独立模式。

使用独立模式,要使用腾讯云的服务的时候,我们无需将用户的账号密码同步到腾讯,但是我们的服务端需要进行一定的处理。 用户在APP客户端输入帐号密码后到APP自有帐号登录服务器验证,验证成功后自有帐号登录服务器使用私钥派发签名(sig)给客户端;客户端提交用户帐号和私钥签名IM云(通过IMSDK或者音视频SDK接口),验证签名成功后向终端派发相应票据,进而使用IM云服务。

  • 开发者需要保证私钥安全,腾讯完全信赖私钥签名;
  • 签名有效期由开发者指定,并加密在私钥签名中;
  • 该集成方式无需将APP自有帐号的帐号密码同步腾讯,最大程度保证开发者用户数据的私密性。

下面贴出官方文档的一个例子说明

假设开发者开发的APP为天天互动,天天互动自有服务器(开发自己开发)支持用户注册功能,同时天天互动接入了腾讯音视频通讯服务。某一天用户A在天天互动注册了帐号,登录天天互动后使用腾讯的音视频服务与家人视频。那么天天互动是如何实现这一功能呢?下面描述具体登录流程:
- 用户A在天天互动的客户端输入自己的帐号及密码后,客户端传给天天互动的帐号服务器进行验证;
- 天天互动的帐号服务器验证用户A的信息成功后,使用天天互动的私钥通过TLS提供的后台API生成签名(sig);
- 天天互动的帐号服务器将生成的签名派发给天天互动的客户端;
- 天天互动的客户端使用音视频云通讯相关服务时,需要提交帐号类型(accounttype)、sdkappid、identifier(也就是我们常说的用户id)和签名(由天天互动账号服务器调用TLS后台API生成并派发的)到音视频SDK或者IMSDK的login接口进行验证,验证成功后即可使用相关服务。

下面我们进行服务器端的编码

几个有用的文档和链接
- TLS后台API开发指引
- windows 64位 API
- 音视频云通讯账号登录集成

服务器端签名的函数,这里使用PHP

<?phpfunction signature($account_type, $identifier, $appid_at_3rd,           $sdk_appid, $expiry_after, $private_key_path){    $command = 'signature.exe'   . ' '. escapeshellarg($private_key_path)   . ' ' . escapeshellarg($expiry_after)   . ' ' . escapeshellarg($sdk_appid)   . ' ' . escapeshellarg($account_type)   . ' ' . escapeshellarg($appid_at_3rd)   . ' ' .escapeshellarg($identifier);    $ret = exec($command, $out, $status);    if( $status == -1){        return null;    }    return $ret;}?>

当然你还需要将服务器sdk中的signarure.exe放到当前目录下,此外还有私钥

当我们服务接收到客户端请求时,登陆成功后会进行签名,需要将签名下发到客户端,客户端利用该签名向腾讯服务器验证

<?phpinclude_once('function.php');if(isset($_POST['username']) && isset($_POST['password'])){    $account['username']=$_POST['username'] ;        $account['password']=$_POST['password'];    //这里处理自己服务器登录的流程    //自己服务器登录成功后向客户端下发加密的rsa串    $result=signature("1071",$account['username'],"1400001973","1400001973",60*60*24*30,"./private_key");    if($result==null){        $res['status']=500;        $res['message']="server error";        echo json_encode($res);    }else{        $res['status']=200;        $res['message']=$result;        echo json_encode($res);    }}else{    $res['status']=404;    $res['message']="params is not right";    echo json_encode($res);}?>

签名的参数在腾讯云的后台管理可以看到

这里写图片描述

这里写图片描述

注意有一个时间戳参数,该参数代表多久之后过期,由开发者控制。

接下来就是客户端的事了。首先就是集成

这里写图片描述

权限

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>    <uses-permission android:name="android.permission.CAMERA"/>    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>    <uses-permission android:name="android.permission.GET_TASKS"/>    <uses-permission android:name="android.permission.INTERNET"/>    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>    <uses-permission android:name="android.permission.READ_LOGS"/>    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>    <uses-permission android:name="android.permission.RECORD_AUDIO"/>    <uses-permission android:name="android.permission.VIBRATE"/>    <uses-permission android:name="android.permission.WAKE_LOCK"/>    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>    <uses-permission android:name="android.permission.BLUETOOTH"/>    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>    <uses-permission android:name="android.permission.BROADCAST_STICKY"/>

声明组件

 <application        android:name=".app.App"></application >
 <activity            android:name=".activity.AvActivity"            android:configChanges="keyboardHidden|orientation|locale|screenSize"            android:screenOrientation="portrait" />

App类的内容很简单,就是获得QavsdkControl

public class App extends Application{    private QavsdkControl mQavsdkControl = null;    @Override    public void onCreate() {        super.onCreate();        mQavsdkControl = new QavsdkControl(this);    }    public QavsdkControl getQavsdkControl() {        return mQavsdkControl;    }}

然后需要拷几个现成的类

这里写图片描述

你需要修改内容,让他不报错,大部分都是资源相关的东西。

然后需要修改两个变量,共两处,值在腾讯云后台获得

这里写图片描述

这里写图片描述

客户端的注册功能由开发者自己实现,因为腾讯云不干涉注册,而登陆需要先登录自己的服务器,然后拿签名向腾讯服务器验证。

private void login() {        String u=username.getText().toString();        String p=password.getText().toString();        if (TextUtils.isEmpty(u)||TextUtils.isEmpty(p)){            Toast.makeText(getApplicationContext(),"账号或密码不能为空!",Toast.LENGTH_LONG).show();            return ;        }        //首先登录自己的服务器        //自己服务器登录成功后,服务器需要返回rsa加密后的串        RequestBody requestBody= new FormEncodingBuilder()                .add("username",u)                .add("password",p)                .build();        String url="http://10.0.0.24/tencent/index.php";        Request request=new Request.Builder().url(url).post(requestBody).build();        mOkHttpClient.newCall(request).enqueue(new Callback() {            @Override            public void onFailure(Request request, IOException e) {                Log.e("TAG", "Error,register failure.");            }            @Override            public void onResponse(Response response) throws IOException {                String result = response.body().string();                LoginModel bean = gson.fromJson(result, LoginModel.class);                Message message = Message.obtain();                message.obj = bean;                message.what = LOGIN;                mHandler.sendMessage(message);            }        });        //获得rsa加密串后发起音视频前需要向腾讯服务器验证。    }
  private Handler mHandler=new Handler(){        @Override        public void handleMessage(Message msg) {            switch (msg.what){                case LOGIN:                    LoginModel bean= (LoginModel) msg.obj;                    if (bean.getStatus()==200){                        sign=bean.getMessage();                        //保存sign                        Log.e("TAG","sign:"+sign);                        Toast.makeText(getApplicationContext(),"登录成功,请稍后!",Toast.LENGTH_LONG).show();                        startTencentContext();                    }else{                        Toast.makeText(getApplicationContext(),"登录失败!"+bean.getMessage(),Toast.LENGTH_LONG).show();                    }                    break;                default:                    break;            }            super.handleMessage(msg);        }    };

登陆成功后就可以拿到该签名了。之后开始向腾讯服务验证

  private void startTencentContext() {        //发起音视频前需要向腾讯服务器验证。        String u=username.getText().toString();        String p=password.getText().toString();        if (TextUtils.isEmpty(u)||TextUtils.isEmpty(p)){            Toast.makeText(getApplicationContext(),"账号或密码不能为空!",Toast.LENGTH_LONG).show();            return ;        }        if(TextUtils.isEmpty(sign)){            Toast.makeText(MainActivity.this, "请先登录", Toast.LENGTH_SHORT).show();            return ;        }        if (!mQavsdkControl.hasAVContext()) {            if (!mQavsdkControl.isDefaultAppid()) {                Toast.makeText(getApplicationContext(), getString(R.string.help_msg_appid_not_default), Toast.LENGTH_LONG).show();            }            if (!mQavsdkControl.isDefaultUid()) {                Toast.makeText(getApplicationContext(), getString(R.string.help_msg_uid_not_default), Toast.LENGTH_LONG).show();            }            mLoginErrorCode = mQavsdkControl.startContext(u, sign);            if (mLoginErrorCode != AVConstants.AV_ERROR_OK) {                Toast.makeText(getApplicationContext(),"错误码:"+mLoginErrorCode, Toast.LENGTH_LONG).show();            }        }    }

验证的结果是广播异步返回的,因此我们需要注册一个广播接收器,接收这个消息

private void initStartContextBroadcast() {        IntentFilter intentFilter = new IntentFilter();        intentFilter.addAction(Util.ACTION_START_CONTEXT_COMPLETE);        intentFilter.addAction(Util.ACTION_CLOSE_CONTEXT_COMPLETE);        registerReceiver(mStartContextBroadcastReceiver, intentFilter);}private BroadcastReceiver mStartContextBroadcastReceiver = new BroadcastReceiver() {        @Override        public void onReceive(Context context, Intent intent) {            String action = intent.getAction();            Log.e("TAG", "WL_DEBUG onReceive action = " + action);            Log.e("TAG", "WL_DEBUG ANR StartContextActivity onReceive action = " + action + " In");            if (action.equals(Util.ACTION_START_CONTEXT_COMPLETE)) {                mLoginErrorCode = intent.getIntExtra( Util.EXTRA_AV_ERROR_RESULT, AVConstants.AV_ERROR_OK);                if (mLoginErrorCode == AVConstants.AV_ERROR_OK) {                    Intent i=new Intent(MainActivity.this,CallActivity.class);                    Bundle bundle=new Bundle();                    bundle.putString("from",username.getText().toString());                    i.putExtras(bundle);                    startActivity(i);                } else {                    Toast.makeText(getApplicationContext(), getString(R.string.help_msg_login_error), Toast.LENGTH_LONG).show();                }            } else if (action.equals(Util.ACTION_CLOSE_CONTEXT_COMPLETE)) {            }            Log.e("TAG", "WL_DEBUG ANR StartContextActivity onReceive action = " + action + " Out");        }    };

之后我们就跳到了另外一个Activity当中,这个Activity中用来处理发起视频通话,处理通话失败等特殊情况的逻辑了,基本上直接从原Demo中复制代码修改即可,直接看代码

public class CallActivity extends AppCompatActivity implements View.OnClickListener{    private String from;    private EditText to;    private Button video,voice;    private QavsdkControl mQavsdkControl;    private int mCreateRoomErrorCode = AVConstants.AV_ERROR_OK;    private int mCloseRoomErrorCode = AVConstants.AV_ERROR_OK;    private int mAcceptErrorCode = AVConstants.AV_ERROR_OK;    private int mInviteErrorCode = AVConstants.AV_ERROR_OK;    private int mRefuseErrorCode = AVConstants.AV_ERROR_OK;    private int mJoinRoomErrorCode = AVConstants.AV_ERROR_OK;    private String mReceiveIdentifier = "";    private String mSelfIdentifier = "";    private boolean mIsVideo = false;    private boolean isSender = false;    private boolean isReceiver = false;    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {        @Override        public void onReceive(Context context, Intent intent) {            String action = intent.getAction();            Log.e("TAG", "WL_DEBUG onReceive action = " + action);            Log.e("TAG", "WL_DEBUG ANR CreateRoomActivity onReceive action = " + action + " In");            if (action.equals(Util.ACTION_ACCEPT_COMPLETE)) {                String identifier = intent.getStringExtra(Util.EXTRA_IDENTIFIER);                mSelfIdentifier = intent.getStringExtra(Util.EXTRA_SELF_IDENTIFIER);                mReceiveIdentifier = identifier;                long roomId = intent.getLongExtra(Util.EXTRA_ROOM_ID, -1);                mAcceptErrorCode = intent.getIntExtra(                        Util.EXTRA_AV_ERROR_RESULT, AVConstants.AV_ERROR_OK);                if (mAcceptErrorCode == AVConstants.AV_ERROR_OK) {                    mQavsdkControl.enterRoom(roomId, identifier, mIsVideo);                } else {                    Toast.makeText(CallActivity.this,R.string.accept_failed,Toast.LENGTH_SHORT).show();                }            } else if (action.equals(Util.ACTION_CLOSE_ROOM_COMPLETE)) {            } else if (action.equals(Util.ACTION_INVITE_ACCEPTED)) {                startActivity(mReceiveIdentifier,mSelfIdentifier);            } else if (action.equals(Util.ACTION_INVITE_CANCELED)) {                    Toast.makeText(getApplicationContext(), R.string.invite_canceled_toast,                            Toast.LENGTH_LONG).show();            } else if (action.equals(Util.ACTION_INVITE_COMPLETE)) {                if (isReceiver) {                    Toast.makeText(getApplicationContext(), R.string.notify_conflict,                            Toast.LENGTH_LONG).show();                }                mInviteErrorCode = intent.getIntExtra(                        Util.EXTRA_AV_ERROR_RESULT, AVConstants.AV_ERROR_OK);                if (mInviteErrorCode == AVConstants.AV_ERROR_OK) {                    //等待对方接受邀请                    Toast.makeText(getApplicationContext(), R.string.dialog_waitting_title,                            Toast.LENGTH_LONG).show();                } else {                    //邀请失败                    Toast.makeText(getApplicationContext(), R.string.invite_failed,                            Toast.LENGTH_LONG).show();                    closeRoom();                }            } else if (action.equals(Util.ACTION_INVITE_REFUSED)) {                    Toast.makeText(getApplicationContext(), R.string.invite_refused_toast,                            Toast.LENGTH_LONG).show();            } else if (action.equals(Util.ACTION_RECV_INVITE)) {                if (isSender) {                    Toast.makeText(getApplicationContext(), R.string.notify_conflict,                            Toast.LENGTH_LONG).show();                }                isReceiver = true;                mIsVideo = intent.getBooleanExtra(Util.EXTRA_IS_VIDEO, false);                new AlertDialog.Builder(CallActivity.this)                        .setTitle(R.string.invite_title)                        .setPositiveButton(android.R.string.ok,                                new DialogInterface.OnClickListener() {                                    public void onClick(DialogInterface dialog,                                                        int whichButton) {                                        mQavsdkControl.accept();                                    }                                })                        .setNegativeButton(android.R.string.cancel,                                new DialogInterface.OnClickListener() {                                    public void onClick(DialogInterface dialog,                                                        int whichButton) {                                        mQavsdkControl.refuse();                                        isSender = isReceiver = false;                                    }                                })                        .setOnCancelListener(                                new DialogInterface.OnCancelListener() {                                    @Override                                    public void onCancel(DialogInterface dialog) {                                        Log.e("TAG", "WL_DEBUG onCancel");                                        mQavsdkControl.refuse();                                        isSender = isReceiver = false;                                    }                                }).create().show();            } else if (action.equals(Util.ACTION_REFUSE_COMPLETE)) {                mRefuseErrorCode = intent.getIntExtra(                        Util.EXTRA_AV_ERROR_RESULT, AVConstants.AV_ERROR_OK);                if (mRefuseErrorCode != AVConstants.AV_ERROR_OK) {                    Toast.makeText(getApplicationContext(), R.string.refuse_failed,                            Toast.LENGTH_LONG).show();                }            } else if (action.equals(Util.ACTION_ROOM_CREATE_COMPLETE)) {                mCreateRoomErrorCode = intent.getIntExtra(                        Util.EXTRA_AV_ERROR_RESULT, AVConstants.AV_ERROR_OK);                if (mCreateRoomErrorCode != AVConstants.AV_ERROR_OK) {                    Toast.makeText(getApplicationContext(), R.string.create_room_failed,                            Toast.LENGTH_LONG).show();                }            } else if (action.equals(Util.ACTION_ROOM_JOIN_COMPLETE)) {                mJoinRoomErrorCode = intent.getIntExtra(                        Util.EXTRA_AV_ERROR_RESULT, AVConstants.AV_ERROR_OK);                if (mJoinRoomErrorCode != AVConstants.AV_ERROR_OK) {                    Toast.makeText(getApplicationContext(), R.string.join_room_failed,                            Toast.LENGTH_LONG).show();                } else {                    startActivity(mReceiveIdentifier,mSelfIdentifier);                }            }            Log.e("TAG", "WL_DEBUG ANR CreateRoomActivity onReceive action = " + action + " Out");        }    };    private void closeRoom() {        mCloseRoomErrorCode = mQavsdkControl.exitRoom();        if (mCloseRoomErrorCode != AVConstants.AV_ERROR_OK) {            Toast.makeText(CallActivity.this,R.string.close_room_failed,Toast.LENGTH_SHORT).show();        }    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_call);        initIntent();        initView();        initInviteBroadcast();        mQavsdkControl = ((App) getApplication()).getQavsdkControl();    }    private void initIntent() {        Intent intent=getIntent();        Bundle bundle=intent.getExtras();        from=bundle.getString("from");        Log.e("TAG","from:"+from);    }    private void initInviteBroadcast() {        IntentFilter intentFilter = new IntentFilter();        intentFilter.addAction(Util.ACTION_ACCEPT_COMPLETE);        intentFilter.addAction(Util.ACTION_CLOSE_ROOM_COMPLETE);        intentFilter.addAction(Util.ACTION_INVITE_ACCEPTED);        intentFilter.addAction(Util.ACTION_INVITE_CANCELED);        intentFilter.addAction(Util.ACTION_INVITE_COMPLETE);        intentFilter.addAction(Util.ACTION_INVITE_REFUSED);        intentFilter.addAction(Util.ACTION_RECV_INVITE);        intentFilter.addAction(Util.ACTION_REFUSE_COMPLETE);        intentFilter.addAction(Util.ACTION_ROOM_CREATE_COMPLETE);        intentFilter.addAction(Util.ACTION_ROOM_JOIN_COMPLETE);        registerReceiver(mBroadcastReceiver, intentFilter);    }    private void initView() {        to= (EditText) findViewById(R.id.to);        video= (Button) findViewById(R.id.video);        voice= (Button) findViewById(R.id.voice);        video.setOnClickListener(this);        voice.setOnClickListener(this);    }    @Override    public void onClick(View v) {        switch (v.getId()){            case R.id.voice:                voice();                break;            case R.id.video:                video();                break;            default:                break;        }    }    private void voice() {        mSelfIdentifier=from;        mReceiveIdentifier=to.getText().toString();        mIsVideo=false;        if (Util.isNetworkAvailable(getApplicationContext())) {            if (TextUtils.isEmpty(mSelfIdentifier)) {                Toast.makeText(getApplicationContext(), getString(R.string.help_msg_send_account_error), Toast.LENGTH_SHORT).show();            } else if (TextUtils.isEmpty(mReceiveIdentifier)) {                Toast.makeText(getApplicationContext(), getString(R.string.help_msg_recv_account_error), Toast.LENGTH_SHORT).show();            } else {                if (mSelfIdentifier.equals(mReceiveIdentifier)) {                    Toast.makeText(getApplicationContext(), getString(R.string.help_msg_account_send_equal_recv), Toast.LENGTH_SHORT).show();                } else {                    invite(mIsVideo);                    isSender = true;                }            }        } else {            Toast.makeText(getApplicationContext(), getString(R.string.notify_no_network), Toast.LENGTH_SHORT).show();        }    }    private void video() {        mSelfIdentifier=from;        mReceiveIdentifier=to.getText().toString();        mIsVideo=true;        if (Util.isNetworkAvailable(getApplicationContext())) {            if (TextUtils.isEmpty(mSelfIdentifier)) {                Toast.makeText(getApplicationContext(), getString(R.string.help_msg_send_account_error), Toast.LENGTH_SHORT).show();            } else if (TextUtils.isEmpty(mReceiveIdentifier)) {                Toast.makeText(getApplicationContext(), getString(R.string.help_msg_recv_account_error), Toast.LENGTH_SHORT).show();            } else {                if (mSelfIdentifier.equals(mReceiveIdentifier)) {                    Toast.makeText(getApplicationContext(), getString(R.string.help_msg_account_send_equal_recv), Toast.LENGTH_SHORT).show();                } else {                    invite(mIsVideo);                    isSender = true;                }            }        } else {            Toast.makeText(getApplicationContext(), getString(R.string.notify_no_network), Toast.LENGTH_SHORT).show();        }    }    private void startActivity(String mReceiveIdentifier,String mSelfIdentifier) {        Log.e("TAG", "WL_DEBUG startActivity");        if ((mQavsdkControl != null) && (mQavsdkControl.getAVContext() != null) && (mQavsdkControl.getAVContext().getAudioCtrl() != null)) {            mQavsdkControl.getAVContext().getAudioCtrl().startTRAEService();        }        startActivityForResult(                new Intent(Intent.ACTION_MAIN)                        .putExtra(Util.EXTRA_IDENTIFIER, mReceiveIdentifier)                        .putExtra(Util.EXTRA_SELF_IDENTIFIER, mSelfIdentifier)                        .setClass(this, AvActivity.class),                0);    }    @Override    protected void onDestroy() {        super.onDestroy();        if (mBroadcastReceiver != null) {            unregisterReceiver(mBroadcastReceiver);        }    }    @Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) {        Log.e("TAG", "WL_DEBUG onActivityResult");        isSender = isReceiver = false;        if ((mQavsdkControl != null) && (mQavsdkControl.getAVContext() != null) && (mQavsdkControl.getAVContext().getAudioCtrl() != null)) {            mQavsdkControl.getAVContext().getAudioCtrl().stopTRAEService();        }        closeRoom();    }    private void invite(boolean isVideo) {        if (TextUtils.isEmpty(mReceiveIdentifier))            return;        mQavsdkControl.invite(mReceiveIdentifier, isVideo);    }}

这样就差不多可以跑一个试试了。

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

其他细节看腾讯的文档把,多看几次就能把握个大概了。

源码下载
http://download.csdn.net/detail/sbsujjbcy/9139613

Github
https://github.com/lizhangqu/VoiceAndVideo

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

2楼mayfla4天前 19:35
云以后会发展的越来越好的。节省资源
1楼u0125834595天前 10:41
66
  相关解决方案