Android藍芽socket實現視訊實時傳輸,以及圖片和文字傳輸
阿新 • • 發佈:2018-11-03
目標
兩臺手機裝置之間能夠正常進行藍芽配對(藍芽模組兒和硬體掛鉤,所以需要兩臺真機)
socket實現藍芽文字傳輸
實現圖片傳輸
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;
}
兩臺手機裝置之間能夠正常進行藍芽配對(藍芽模組兒和硬體掛鉤,所以需要兩臺真機)
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;
}