注:java
看完文章你能夠學到git
因爲項目需求,須要在執法儀本地錄像的時候,執法軟件能正常的使用設備自己的 Camera 資源。因爲 Android 系統自身不容許多個軟件同時使用 Camera 資源,故開發一套內存共享子碼流傳輸協議,當執法軟件須要視頻流的時候,向執法儀設備請求往 MemoryFile 中寫入 YUV 格式的視頻流,執法軟件每隔一段時間循環的去指定的內存中 read YUV 視頻流。github
完整代碼入口服務器
本地相機未打開ide
本地相機打開spa
執法儀服務端收到客戶端的開啓視頻流寫入內存的指令3d
//處理客服端發送過來的須要子碼流的數據
private void onHandleAction(Context context, Intent intent) {
switch (intent.getAction()) {
/** * 須要子碼流 */
case Constants.ACTION_CAMERE_CORE_SHOW:
//若是正在發送視頻流,就不須要執行後面代碼了
if (!MemoryFileServiceManager.getInsta(context).isSendVideoFrame())
MemoryFileServiceManager.getInsta(context).setSendVideoFrame(true, intent);
break;
}
}
複製代碼
服務端獲取開啓視頻的條件code
private void sendVideoFrame(Intent intent) {
if (intent != null && intent.getExtras() != null) {
Bundle extras = intent.getExtras();
//獲取須要預覽的寬
Constants.PREVIEWHEIGHT = extras.getInt(Constants.Config.PREVIEW_WIDTH, 1280);
//獲取須要預覽的高
Constants.PREVIEWHEIGHT = extras.getInt(Constants.Config.PREVIEW_HEIGHT, 720);
//須要綁定對方服務的進程
Constants.BIND_OTHER_SERVICE_PCK = extras.getString(Constants.Config.BIND_OTHER_SERVICE_PCK, "");
//須要綁定對方服務的全路徑
Constants.BIND_OTHER_SERVICE_CLASS = extras.getString(Constants.Config.BIND_OTHER_SERVICE_CLASS, "");
//須要開啓 Camera ID 的前置仍是後置 0:後置 1:前置
Constants.CAMERA_ID = extras.getInt(Constants.Config.CAMERA_ID, 0);
}
}
複製代碼
服務器是否開啓相機,若是已經開啓則不須要開啓component
//是否攝像頭
if (mCamera == null)
openCamera();
複製代碼
服務端初始化一塊內存,用於寫入 YUV 視頻流。cdn
mMemoryFile = initMemoryFile(Constants.MEMORY_FILE_NAME, Constants.MEMORY_SIZE);
複製代碼
綁定對方服務,提供文件描述符號
/** * 綁定對方服務,提供 文件描述符 */
private void bindOtherService() {
try {
if (TextUtils.isEmpty(Constants.BIND_OTHER_SERVICE_PCK) || TextUtils.isEmpty(Constants.BIND_OTHER_SERVICE_CLASS))
throw new NullPointerException("PCK or CLSS is null ?");
Intent intent = new Intent();
ComponentName cmp = new ComponentName(Constants.BIND_OTHER_SERVICE_PCK, Constants.BIND_OTHER_SERVICE_CLASS);
intent.setComponent(cmp);
context.bindService(intent, mCameraServiceConnection, Context.BIND_AUTO_CREATE);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
複製代碼
綁定對方服務成功,交於文件描述符 ParcelFileDescriptor
mCameraService = ICameraCoreService.Stub.asInterface(binder);
if (mMemoryFile != null) {
try {
//反射拿到文件描述符號
mParcelFileDescriptor = MemoryFileHelper.getParcelFileDescriptor(mMemoryFile);
if (mParcelFileDescriptor != null) {
mCameraService.addExportMemoryFile(mParcelFileDescriptor, Constants.PREVIEWWIDTH, Constants.PREVIEWHEIGHT, Constants.MEMORY_SIZE);
}
複製代碼
發送數據,當標誌位爲 byte[0] == 0 表明服務端可將 YUV 寫入內存, == 1 ,表明客服端能夠讀取可用的 YUV 數據。
/** * 讀標誌位 寫入視頻流 * * @param memoryFile */
public void writeBytes(MemoryFile memoryFile) {
try {
if (mYUVQueue.size() > 0) {
BufferBean mBufferBean = new BufferBean(Constants.BUFFER_SIZE);
//讀取標誌符號
memoryFile.readBytes(mBufferBean.isCanRead, 0, 0, 1);
//當第一位爲 0 的時候,表明客服端已經讀取了,能夠正常將視頻流寫入內存中
if (mBufferBean.isCanRead[0] == 0) {
//拿到視頻流
byte[] video = mYUVQueue.poll();
if (video != null)
//將視頻流寫入內存中
memoryFile.writeBytes(video, 0, 0, video.length);
//標誌位復位,等待客服端讀取視頻流
mBufferBean.isCanRead[0] = 1;
memoryFile.writeBytes(mBufferBean.isCanRead, 0, 0, 1);
} else {
Log.d(TAG, "readShareBufferMsg isCanRead:" + mBufferBean.isCanRead[0] + ";length:"
+ mBufferBean.mBuffer.length);
}
}
} catch (IOException e) {
e.printStackTrace();
sendBroadcast(Constants.ACTION_FEEDBACK, e.getMessage());
}
}
複製代碼
開啓成功或者失敗等其餘錯誤消息反饋給客服端
//返回給客服端
public void sendBroadcast(String action,String content) {
Intent intent = new Intent();
intent.setAction(action);
ComponentName componentName = new ComponentName("com.t01.sharevideostream",
"com.t01.sharevideostream.revices.FeedBackReceiver");
intent.setComponent(componentName);
Bundle extras = new Bundle();
extras.putString(Constants.ACTION_FEEDBACK_CONTENT, content);
intent.putExtras(extras);
context.sendBroadcast(intent);
}
複製代碼