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

<?php
function 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放到当前目录下,此外还有私钥

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

<?php
include_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

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

时间: 2024-12-23 15:23:45

Android 即时音视频解决方案2——腾讯云的相关文章

Android 即时音视频解决方案1——环信

需求 即时音视频通话 解决方案 环信,官方地址http://www.easemob.com/ SDK下载 http://downloads.easemob.com/downloads/easemob-sdk-2.2.2.zip SDK集成 解压下载的文件,将libs下的easemobchat_2.2.2.jar拷到Android Studio项目中的libs中,并在main目录下新建jniLibs目录,将so文件拷到其中.如图 代码抽取 我们只需要即时音视频的功能,因此环信提供的Demo中有多余

浅谈AnyChat 音视频解决方案for Android 视频显示问题

近日,发现有较多朋友反馈在使用AnyChat的音视频解决方案,特别是在将标准android demo的VideoActivity视频部分代码移植到自己的工程中遇到本地视频黑屏或者远程视频显示不了的问题.这些问题在我当时的开发过程中同样遇到了,在这里就简单把当时我自己解决问题的心得做一个简单汇总说明以及相应解决方案. 1.本地视频黑屏.不显示 问题可能原因:没有设置音视频参数,没有使用Java采集模式 解决方法:将标准demo里面hallactivity类中的ApplyVideoConfig函数移

Android WebRTC 音视频开发总结(一)

Android WebRTC 音视频开发总结(一) https://github.com/gandg/webrtc-ios 接触Android WebRTC有一段时间了,现在将研究过程中的一些经验和知识总结出来,希望大家有所帮助.本章主要介绍下面四点: 一.WebRTC是什么? WebRTC是Google提供的一个跨平台的音视频开源解决方案,旨在使其成为音视频通讯的标准.其实在Google将WebRTC开源之前,微软和苹果各自的通讯产品已占用很大市场份额(如Skype),估计Google也是为了

Android WebRTC 音视频开发总结(五)

这几天用WebRTC做了个视频监控的功能,分享出来,供想了解这方面内容的朋友参考. 一.基本模块: 1.视频采集端:相当于是客户端,用来采集视频,只需要发送视频,不需要接收. 2.视频监控端:接收采集端传入的视频数据,相当于监控客户端,不需要发送视频数据给客户端. 3.服务端:负责客户端注册.信令控制.数据包转发.UDP打洞等,支持TCP,UDP连接. 二.环境要求: 1.两台Andorid4.0 以上的手机,分别做采集端和监控端. 2.一台PC 做服务端. 3.PC.手机在同一个局域网内.理论

实战解析 | 同步音视频解决方案

目前,国内大部分云厂商大多都提供音视频异步处理解决方案,使用中,音视频异步处理任务需要排队并且处理时间过长.当音视频需要适用各平台时,就需要对它进行转多种码率.转多种格式.打水印.切片等.处理过程通常会消耗"N×音视频时长"(N 是处理次数)的时间,用户平均需要等待"N×音视频时长"的时间,十分影响用户体验. 秒级完成音视频转码处理 又拍云的服务器上运行着众多商业直播和UGC项目,为帮助用户实现秒级音视频处理,解决任务排队.处理时间长的问题,又拍云推出了同步音视频处

跨平台的即时通讯音视频解决方案

改变你视界的AnyChat,通过封装音视频编解码.流媒体处理以及P2P等专业复杂技术,为上层应用提供简单的API控制接口,实现一对一.一对多的实时音视频交互.文件传输.透明通道.音视频录制等功能.在移动互联网.物联网.在线教育.远程医疗.视频客服以及智能家居等业务领域拥有广泛的应用空间. 产品核心竞争力 一.音视频通信 承载数千客户的多形态音视频通信应用,近十年技术积累值得信赖,AnyChat将为您提供专业卓越的跨平台音视频通信服务体验.采用和优化H.264视频编解码,AAC音频编码标准与P2P

Android WebRTC 音视频开发总结

www.cnblogs.com/lingyunhu/p/3621057.html 前面介绍了WebRTCDemo的基本结构,本节主要介绍WebRTC音视频服务端的处理,,转载请说明出处(博客园RTC.Blacker). 通过前面的例子我们知道运行WebRTCDemo即可看到P2P的效果,实际应用中我们不可能让用户自己去里面设置对方的IP和音视频端口, 而且即使设置了对方的IP和端口也不一定能运行起来,因为P2P如果双方不在同一个网段则还需穿透NAT,那服务端具体该如何部署呢? 1.信令服务: 想

音视频解决方案Web平台之背景水印更改

AnyChat SDK(AnyChat音视频互动开发平台)是一套跨平台的(*)即时通讯解决方案,基于先进的H.264视频编码标准.AAC音频编码标准与P2P技术,支持高清视频,整合了佰锐科技在音视频编码.多媒体通讯领域领先的开发技术和丰富的产品经验而设计的高质量.宽适应性.分布式.模块化的网络音视频互动平台.支持Windows.Web.Android.iOS.Linux等跨平台互联互通 其中自从AnyChat r4092版本开始,AnyChat for Web SDK支持设置本地图片为界面背景.

即时音视频通讯的未来

近年来,音视频即时通讯应用在各大行业逐渐应用,甚至在传统的教育.医疗行业也成为音视频即时通讯应用的重要行业,现在如聊天室,金融行业,银行业务办理,企业内部会议,数字电视等不断出现音视频的应用!音视频互动平台已成为各大行业音视频即时通讯的新热点,随着社会的发展与网络技术的进步,音视频技术以全新领域凸显了网络信息时代的飞跃发展. 目前,音视频即时通讯已是旺盛的市场必不可少的需求.从行业分布来看,集团公司的贯穿沟通纽带的即时通讯,医疗行业实现社区医院跟市医院的简单点对点音视频即时通讯.教育机构学校与教