1. 程式人生 > >Android藍芽socket實現視訊實時傳輸,以及圖片和文字傳輸

Android藍芽socket實現視訊實時傳輸,以及圖片和文字傳輸

目標
兩臺手機裝置之間能夠正常進行藍芽配對(藍芽模組兒和硬體掛鉤,所以需要兩臺真機)
socket實現藍芽文字傳輸
實現圖片傳輸

實現實時視訊傳輸

程式碼下載:https://download.csdn.net/download/m0_37781149/10434336


藍芽傳輸注意事項
Bluetooth的MTU(最大傳輸單元)是20位元組,即一次最多能傳送20個位元組,若超過20個位元組,建議採用分包傳輸的方式。在傳輸圖片和視訊的時候處理方式是將圖片轉換成位元組byte,並在圖片前面新增一個頭,加入相應的標誌位,例如圖片大小。然後在接受端進行整合,獲取圖片大小(位元組長度),進行相應判斷,整合成圖片再顯示。下面分模組兒講解:
藍芽掃描

 通過註冊廣播來獲取藍芽列表,藍芽連線狀態發生改變時候系統都會發送相應廣播,獲取相應的藍芽列表並新增到list中,顯示出來

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();


            //當發現藍芽裝置
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

                if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                    String strNoFound = getIntent().getStringExtra("no_devices_found");
                    if (strNoFound == null)
                        strNoFound = "No devices found";

                    if (mPairedDevicesArrayAdapter.getItem(0).equals(strNoFound)) {
                        mPairedDevicesArrayAdapter.remove(strNoFound);
                    }
                    mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
                }

            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                setProgressBarIndeterminateVisibility(false);
                String strSelectDevice = getIntent().getStringExtra("select_device");
                if (strSelectDevice == null)
                    strSelectDevice = "Select a device to connect";
                setTitle(strSelectDevice);
            }
        }
    };    

    //掃描藍芽裝置
    private void doDiscovery() {
        mPairedDevicesArrayAdapter.clear();
        if (pairedDevices.size() > 0) {
            for (BluetoothDevice device : pairedDevices) {
                mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
            }
        } else {
            String strNoFound = getIntent().getStringExtra("no_devices_found");
            if (strNoFound == null)
                strNoFound = "No devices found";
            mPairedDevicesArrayAdapter.add(strNoFound);
        }

        String strScanning = getIntent().getStringExtra("scanning");
        if (strScanning == null)
            strScanning = "Scanning for devices...";
        setProgressBarIndeterminateVisibility(true);
        setTitle(strScanning);

        if (mBtAdapter.isDiscovering()) {
            mBtAdapter.cancelDiscovery();
        }
        mBtAdapter.startDiscovery();
    }

點選對應的條目可以回退到MainActivity,並將對應的mac地址攜帶回來,用於藍芽配對連線

    //攜帶藍芽地址返回主介面
    private AdapterView.OnItemClickListener mDeviceClickListener = new AdapterView.OnItemClickListener() {
        public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {
            if (mBtAdapter.isDiscovering())
                mBtAdapter.cancelDiscovery();

            String strNoFound = getIntent().getStringExtra("no_devices_found");
            if (strNoFound == null)
                strNoFound = "No devices found";
            if (!((TextView) v).getText().toString().equals(strNoFound)) {
                String info = ((TextView) v).getText().toString();
                String address = info.substring(info.length() - 17);
                Intent intent = new Intent();
                intent.putExtra(BluetoothState.EXTRA_DEVICE_ADDRESS, address);
                setResult(Activity.RESULT_OK, intent);
                finish();
            }
        }
    };


藍芽連線核心工具類BluetoothUtil,藍芽狀態BluetoothState和BluetoothService
藍芽配對基本過程:在APP啟動的時候,開啟一個執行緒一直監聽藍芽的連線狀態,這個子執行緒是啟動時是死迴圈狀態,在藍芽連線成功時會停止,在藍芽意外斷開時候會重新處於監聽狀態
     
    public void onStart() {
        super.onStart();
        if (!mBt.isBluetoothEnabled()) {
            //開啟藍芽
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intent, BluetoothState.REQUEST_ENABLE_BT);
        } else {
            if (!mBt.isServiceAvailable()) {
                //開啟監聽
                mBt.setupService();
                mBt.startService(BluetoothState.DEVICE_ANDROID);
            }
        }
    }

     public void setupService() {
        mChatService = new BluetoothService(mContext, mHandler);
    }

     public BluetoothService(Context context, Handler handler) {
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mState = BluetoothState.STATE_NONE;
        mHandler = handler;
    }

    //開啟藍芽一直監聽是否連線的狀態
    public void startService(boolean isAndroid) {
        if (mChatService != null) {
            if (mChatService.getState() == BluetoothState.STATE_NONE) {
                isServiceRunning = true;
                mChatService.start(isAndroid);
                BluetoothUtil.this.isAndroid = isAndroid;
            }
        }
    }

     //開啟子執行緒
    public synchronized void start(boolean isAndroid) {
        // Cancel any thread attempting to make a connection
        if (mConnectThread != null) {
            mConnectThread.cancel();
            mConnectThread = null;
        }
        if (mConnectedThread != null) {
            mConnectedThread.cancel();
            mConnectedThread = null;
        }

        setState(BluetoothState.STATE_LISTEN);
        
        //開啟子執行緒
        if (mSecureAcceptThread == null) {
            mSecureAcceptThread = new AcceptThread(isAndroid);
            mSecureAcceptThread.start();
            BluetoothService.this.isAndroid = isAndroid;
        }
    }

    //監聽藍芽連線的執行緒
    private class AcceptThread extends Thread {
        private BluetoothServerSocket mmServerSocket;
        private String mSocketType;
        boolean isRunning = true;

        public AcceptThread(boolean isAndroid) {
            BluetoothServerSocket tmp = null;
            try {
                if (isAndroid)
                    //獲取藍芽socket
                    tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE, UUID_ANDROID_DEVICE);
                else
                    tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE, UUID_OTHER_DEVICE);
            } catch (IOException e) {
            }
            mmServerSocket = tmp;
        }

        public void run() {
            setName("AcceptThread" + mSocketType);
            BluetoothSocket socket = null;
            //死迴圈監聽藍芽連線狀態,首次進入一定滿足條件,藍芽連上後,迴圈停止
            while (mState != BluetoothState.STATE_CONNECTED && isRunning) {
                try {
                    socket = mmServerSocket.accept();
                } catch (IOException e) {
                    break;
                }

                if (socket != null) {
                    synchronized (BluetoothService.this) {
                        switch (mState) {
                            case BluetoothState.STATE_LISTEN:
                            case BluetoothState.STATE_CONNECTING:
                                connected(socket, socket.getRemoteDevice(),
                                        mSocketType);
                                break;
                            case BluetoothState.STATE_NONE:
                            case BluetoothState.STATE_CONNECTED:
                                try {
                                    socket.close();
                                } catch (IOException e) {
                                }
                                break;
                        }
                    }
                }
            }
        }

        public void cancel() {
            try {
                mmServerSocket.close();
                mmServerSocket = null;
            } catch (IOException e) {
            }
        }

        public void kill() {
            isRunning = false;
        }
    }

核心部分:藍芽傳送和接受資料,藍芽傳送資料處理過程,比如傳送一張圖片,肯定是大於20位元組的,藍芽是採用分包傳送機制,在處理的時候,我們把圖片轉換成位元組,並在圖片前面新增一個頭,這個頭是固定位元組的長度,不能太小,因為拍攝圖片的時候,圖片有大有小,避免和圖面裝換成位元組後產生衝突。這個頭裡面主要攜帶兩個資訊,當然也可以是多個,自由定義。兩個資訊分別是,圖片的大小,即位元組長度length,還有一個是動作,例如傳照片,傳文字,實時視訊傳輸,程式碼如下:

    //新增頭髮送資料
    public void send(byte[] data, String str) {
        int length = data.length;
        byte[] length_b = null;
        try {
            length_b = intToByteArray(length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (length_b == null) return;

        //獲得一個位元組長度為14的byte陣列 headInfoLength為14
        byte[] headerInfo = new byte[headInfoLength];

        //前六位新增012345的標誌位
        for (int i = 0; i < headInfoLength - 8; i++) {
            headerInfo[i] = (byte) i;
        }
        
        //7到10位新增圖片大小的位元組長度
        for (int i = 0; i < 4; i++) {
            headerInfo[6 + i] = length_b[i];
        }
        
        //11到14位新增動作資訊
        if (str.equals("text")) {
            for (int i = 0; i < 4; i++) {
                headerInfo[10 + i] = (byte) 0;
            }
        } else if (str.equals("photo")) {
            for (int i = 0; i < 4; i++) {
                headerInfo[10 + i] = (byte) 1;
            }
        } else if (str.equals("video")) {
            for (int i = 0; i < 4; i++) {
                headerInfo[10 + i] = (byte) 2;
            }
        } 

        //將對應資訊新增到圖片前面
        byte[] sendMsg = new byte[length + headInfoLength];
        for (int i = 0; i < sendMsg.length; i++) {
            if (i < headInfoLength) {
                sendMsg[i] = headerInfo[i];
            } else {
                sendMsg[i] = data[i - headInfoLength];
            }

        }
        mChatService.write(sendMsg);
    }
    
    //藍芽socket傳送資料
     public void write(byte[] out) {
        ConnectedThread r;
        synchronized (this) {
            if (mState != BluetoothState.STATE_CONNECTED) return;
            r = mConnectedThread;
        }
        r.write(out);
    }

      public void write(byte[] buffer) {
            try {
                mmOutStream.write(buffer);
            } catch (IOException e) {
            }
        }

藍芽接收資料:接收到的資料都是位元組資料,我們需要把資料進行整合成對應的圖片或視訊資訊,因為傳送時分包機制,所以整合的時候要確保整張圖片傳送完畢才開始整合,具體流程是先獲取前六位標誌位,然後獲取第7到10位的圖片大小,再獲取第11位到14位的動作資訊,具體程式碼如下:
     
        public void run() {
            byte[] buffer;
            ArrayList<Integer> arr_byte = new ArrayList<Integer>();
            while (true) {
                try {
                    boolean valid = true;
                    //判斷前六位是不是012345
                    for (int i = 0; i < 6; i++) {
                        int t = mmInStream.read();
                        if (t != i) {
                            valid = false;
                            //前六位判斷完了跳出迴圈
                            break;
                        }
                    }
                    if (valid) {
                        //獲取圖片大小
                        byte[] bufLength = new byte[4];
                        for (int i = 0; i < 4; i++) {
                            bufLength[i] = ((Integer) mmInStream.read()).byteValue();
                        }
                
                        int TextCount = 0;
                        int PhotoCount = 0;
                        int VideoCount = 0;
                        //獲取動作資訊
                        for (int i = 0; i < 4; i++) {
                            int read = mmInStream.read();
                            if (read == 0) {
                                TextCount++;
                            } else if (read == 1) {
                                PhotoCount++;
                            } else if (read == 2) {
                                VideoCount++;
                            }
                        }

                        //獲取圖片的位元組
                        int length = ByteArrayToInt(bufLength);
                        buffer = new byte[length];
                        for (int i = 0; i < length; i++) {
                            buffer[i] = ((Integer) mmInStream.read()).byteValue();
                        }
            
                        //通過handler發出去
                        Message msg = Message.obtain();
                        msg.what = BluetoothState.MESSAGE_READ;
                        msg.obj = buffer;

                        if (TextCount == 4) {
                            msg.arg1 = 0;
                            mHandler.sendMessage(msg);
                        } else if (PhotoCount == 4) {
                            msg.arg1 = 1;
                            mHandler.sendMessage(msg);
                        } else if (VideoCount == 4) {
                            msg.arg1 = 2;
                            mHandler.sendMessage(msg);
                        }

                    }

                } catch (IOException e) {
                    connectionLost();
                    BluetoothService.this.start(BluetoothService.this.isAndroid);
                    break;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }


下面分別是傳送端和接受端的完整程式碼:
傳送端SendClient程式碼部分:
layout檔案:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.rejointech.sendclient.MainActivity">

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="300dp"/>

    <EditText
        android:layout_marginTop="20dp"
        android:id="@+id/input"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="請輸入要傳送的文字資訊"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:orientation="horizontal">

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="bluetooth"
            android:text="藍芽"
            android:textColor="#000"
            android:textSize="18sp"/>

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="文字"
            android:onClick="sendText"
            android:textColor="#000"
            android:textSize="18sp"/>

        <Button
            android:onClick="sendPhoto"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="圖片"
            android:textColor="#000"
            android:textSize="18sp"/>

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="視訊"
            android:onClick="sendVideo"
            android:textColor="#000"
            android:textSize="18sp"/>
    </LinearLayout>
</LinearLayout>

發射端的主介面:

    public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
    private static final String TAG = MainActivity.class.getName();
    private static final int REQUEST_BLUETOOTH_ENABLE = 100;
    private BluetoothUtil mBt;
    private Camera mCamera;
    private SurfaceHolder mSurfaceHolder;
    private int mWidth;
    private int mHeight;
    private EditText mInput;
    private boolean isBluetoothConnnect;
    public Camera.Size size;
    private boolean mark = true;
    private int count;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBt = new BluetoothUtil(this);
        mInput = (EditText) findViewById(R.id.input);
        SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
        mSurfaceHolder = surfaceView.getHolder();
        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        mSurfaceHolder.addCallback(this);
        initBlue();
    }

    private void initBlue() {
        /**
         * reveice data
         */
        mBt.setOnDataReceivedListener(new BluetoothUtil.OnDataReceivedListener() {
            public void onDataReceived(byte[] data, String message) {

            }
        });

        mBt.setBluetoothConnectionListener(new BluetoothUtil.BluetoothConnectionListener() {
            public void onDeviceConnected(String name, String address) {
                isBluetoothConnnect = true;
                Toast.makeText(getApplicationContext(), "連線到 " + name + "\n" + address, Toast.LENGTH_SHORT).show();
            }

            public void onDeviceDisconnected() {
                isBluetoothConnnect = false;
                //斷開藍芽連線
                Toast.makeText(getApplicationContext(), "藍芽斷開", Toast.LENGTH_SHORT).show();

            }

            public void onDeviceConnectionFailed() {
                Toast.makeText(getApplicationContext(), "無法連線", Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == BluetoothState.REQUEST_CONNECT_DEVICE) {
            if (resultCode == Activity.RESULT_OK)
                mBt.connect(data);
        } else if (requestCode == BluetoothState.REQUEST_ENABLE_BT) {
            if (resultCode == Activity.RESULT_OK) {
                mBt.setupService();
                mBt.startService(BluetoothState.DEVICE_ANDROID);
            } else {
                finish();
            }
        }
    }

    public void onStart() {
        super.onStart();
        if (!mBt.isBluetoothEnabled()) {
            //開啟藍芽
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intent, BluetoothState.REQUEST_ENABLE_BT);
        } else {
            if (!mBt.isServiceAvailable()) {
                //開啟監聽
                mBt.setupService();
                mBt.startService(BluetoothState.DEVICE_ANDROID);
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mBt.stopService();
        releaseCamera();
    }

    private void releaseCamera() {
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.setPreviewCallbackWithBuffer(null);
            mCamera.stopPreview();// 停掉原來攝像頭的預覽
            mCamera.release();
            mCamera = null;
        }
    }


    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            mCamera = Camera.open();
            Camera.Parameters mPara = mCamera.getParameters();
            List<Camera.Size> pictureSizes = mCamera.getParameters().getSupportedPictureSizes();
            List<Camera.Size> previewSizes = mCamera.getParameters().getSupportedPreviewSizes();

            int previewSizeIndex = -1;
            Camera.Size psize;
            int height_sm = 999999;
            int width_sm = 999999;
            //獲取裝置最小解析度圖片,圖片越清晰,傳輸越卡
            for (int i = 0; i < previewSizes.size(); i++) {
                psize = previewSizes.get(i);
                if (psize.height <= height_sm && psize.width <= width_sm) {
                    previewSizeIndex = i;
                    height_sm = psize.height;
                    width_sm = psize.width;
                }
            }

            if (previewSizeIndex != -1) {
                mWidth = previewSizes.get(previewSizeIndex).width;
                mHeight = previewSizes.get(previewSizeIndex).height;
                mPara.setPreviewSize(mWidth, mHeight);
            }
            mCamera.setParameters(mPara);
            mCamera.setPreviewDisplay(mSurfaceHolder);
            mCamera.startPreview();

            size = mCamera.getParameters().getPreviewSize();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }

    //藍芽搜尋配對
    public void bluetooth(View view) {
        if (mBt.getServiceState() == BluetoothState.STATE_CONNECTED) {
            mBt.disconnect();
        } else {
            Intent intent = new Intent(getApplicationContext(), BluetoothActivity.class);
            startActivityForResult(intent, BluetoothState.REQUEST_CONNECT_DEVICE);
        }
    }

    //傳送文字資訊
    public void sendText(View view) {
        if (!isBluetoothConnnect) {
            Toast.makeText(this, "請連線藍芽", Toast.LENGTH_SHORT).show();
            return;
        }