diff --git a/app/src/main/java/com/jiangdg/usbcamera/view/USBCameraActivity.java b/app/src/main/java/com/jiangdg/usbcamera/view/USBCameraActivity.java index f12b719..2b2d6c5 100644 --- a/app/src/main/java/com/jiangdg/usbcamera/view/USBCameraActivity.java +++ b/app/src/main/java/com/jiangdg/usbcamera/view/USBCameraActivity.java @@ -2,8 +2,12 @@ package com.jiangdg.usbcamera.view; import android.hardware.usb.UsbDevice; import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; +import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; @@ -12,8 +16,14 @@ import com.jiangdg.usbcamera.R; import com.jiangdg.usbcamera.USBCameraManager; import com.serenegiant.usb.CameraDialog; import com.serenegiant.usb.USBMonitor; +import com.serenegiant.usb.common.AbstractUVCCameraHandler; import com.serenegiant.usb.widget.CameraViewInterface; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; @@ -35,16 +45,32 @@ public class USBCameraActivity extends AppCompatActivity implements CameraDialog private USBCameraManager mUSBManager; private CameraViewInterface mUVCCameraView; + private boolean isRequest; + // USB设备监听器 private USBCameraManager.OnMyDevConnectListener listener = new USBCameraManager.OnMyDevConnectListener() { @Override public void onAttachDev(UsbDevice device) { - showShortMsg("检测到设备:"+device.getDeviceName()); + if(mUSBManager == null || mUSBManager.getUsbDeviceCount() == 0){ + showShortMsg("未检测到USB摄像头设备"); + return; + } + + if(! isRequest){ + isRequest = true; + if(mUSBManager != null){ + mUSBManager.requestPermission(0); + } + } } @Override public void onDettachDev(UsbDevice device) { - showShortMsg(device.getDeviceName()+"已拨出"); + if(isRequest){ + isRequest = false; + mUSBManager.closeCamera(); + showShortMsg(device.getDeviceName()+"已拨出"); + } } @Override @@ -57,6 +83,7 @@ public class USBCameraActivity extends AppCompatActivity implements CameraDialog // 处理与设备断开后的逻辑 } }; + private FileOutputStream fos; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -101,15 +128,14 @@ public class USBCameraActivity extends AppCompatActivity implements CameraDialog switch (vId) { // 开启或关闭Camera case R.id.camera_view: - if(mUSBManager != null){ - boolean isOpened = mUSBManager.isCameraOpened(); - if(! isOpened){ - CameraDialog.showDialog(USBCameraActivity.this); - }else { - mUSBManager.closeCamera(); - } - } - +// if(mUSBManager != null){ +// boolean isOpened = mUSBManager.isCameraOpened(); +// if(! isOpened){ +// CameraDialog.showDialog(USBCameraActivity.this); +// }else { +// mUSBManager.closeCamera(); +// } +// } break; case R.id.btn_capture_pic: if(mUSBManager == null || ! mUSBManager.isCameraOpened()){ @@ -127,15 +153,46 @@ public class USBCameraActivity extends AppCompatActivity implements CameraDialog showShortMsg("录制异常,摄像头未开启"); return; } + File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + +File.separator+System.currentTimeMillis()+".txt"); + try { + fos = new FileOutputStream(file); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + if(! mUSBManager.isRecording()){ String videoPath = USBCameraManager.ROOT_PATH+System.currentTimeMillis() +USBCameraManager.SUFFIX_MP4; - mUSBManager.startRecording(videoPath); + mUSBManager.startRecording(videoPath, new AbstractUVCCameraHandler.OnEncodeResultListener() { + @Override + public void onEncodeResult(byte[] data, int offset, int length, long timestamp, int type) { + String tip = null; + if(data == null){ + tip = "data = null"; + }else{ + tip = "大小"+data.length+ "类型"+type + ";"; + } + try { + if(fos != null){ + fos.write(tip.getBytes()); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + }); mBtnRecord.setText("正在录制"); } else { mUSBManager.stopRecording(); - + try { + if(fos != null){ + fos.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } mBtnRecord.setText("开始录制"); } break; diff --git a/libusbcamera/src/main/java/com/jiangdg/usbcamera/USBCameraManager.java b/libusbcamera/src/main/java/com/jiangdg/usbcamera/USBCameraManager.java index c3b675f..7774df0 100644 --- a/libusbcamera/src/main/java/com/jiangdg/usbcamera/USBCameraManager.java +++ b/libusbcamera/src/main/java/com/jiangdg/usbcamera/USBCameraManager.java @@ -1,16 +1,23 @@ package com.jiangdg.usbcamera; import android.app.Activity; +import android.content.Context; import android.graphics.SurfaceTexture; import android.hardware.usb.UsbDevice; import android.os.Environment; +import com.jiangdg.libusbcamera.R; import com.serenegiant.usb.CameraDialog; +import com.serenegiant.usb.DeviceFilter; import com.serenegiant.usb.USBMonitor; +import com.serenegiant.usb.common.AbstractUVCCameraHandler; import com.serenegiant.usb.common.UVCCameraHandler; import com.serenegiant.usb.widget.CameraViewInterface; import java.io.File; +import java.util.List; + +import static android.R.attr.filter; /**USB摄像头工具类 * @@ -35,6 +42,8 @@ public class USBCameraManager{ // Camera业务逻辑处理 private UVCCameraHandler mCameraHandler; + private Context mContext; + private USBCameraManager(){} public static USBCameraManager getInstance(){ @@ -60,6 +69,8 @@ public class USBCameraManager{ public void init(Activity activity, final CameraViewInterface cameraView, final OnMyDevConnectListener listener){ if(cameraView == null) throw new NullPointerException("CameraViewInterface cannot be null!"); + mContext = activity.getApplicationContext(); + mUSBMonitor = new USBMonitor(activity.getApplicationContext(), new USBMonitor.OnDeviceConnectListener() { // 当检测到USB设备,被回调 @Override @@ -128,6 +139,40 @@ public class USBCameraManager{ } } + /** + * 请求开启第index USB摄像头 + */ + public void requestPermission(int index){ + List devList = getUsbDeviceList(); + if(devList==null || devList.size() ==0){ + return; + } + int count = devList.size(); + if(index >= count) + new IllegalArgumentException("index illegal,should be < devList.size()"); + if(mUSBMonitor != null) { + mUSBMonitor.requestPermission(getUsbDeviceList().get(index)); + } + } + + /** + * 返回 + * */ + public int getUsbDeviceCount(){ + List devList = getUsbDeviceList(); + if(devList==null || devList.size() ==0){ + return 0; + } + return devList.size(); + } + + private List getUsbDeviceList(){ + List deviceFilters = DeviceFilter.getDeviceFilters(mContext, R.xml.device_filter); + if(mUSBMonitor == null || deviceFilters == null) + return null; + return mUSBMonitor.getDeviceList(deviceFilters.get(0)); + } + /** * 抓拍照片 * */ @@ -137,9 +182,9 @@ public class USBCameraManager{ } } - public void startRecording(String videoPath){ + public void startRecording(String videoPath, AbstractUVCCameraHandler.OnEncodeResultListener listener){ if(mCameraHandler != null && ! isRecording()){ - mCameraHandler.startRecording(videoPath); + mCameraHandler.startRecording(videoPath,listener); } } @@ -169,6 +214,8 @@ public class USBCameraManager{ * 释放资源 * */ public void release(){ + // 关闭摄像头 + closeCamera(); //释放CameraHandler占用的相关资源 if(mCameraHandler != null){ mCameraHandler.release(); diff --git a/libusbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java b/libusbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java index cb10cac..525787e 100644 --- a/libusbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java +++ b/libusbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java @@ -45,7 +45,7 @@ import java.util.concurrent.CopyOnWriteArraySet; * Camera业务处理抽象类 * * */ -abstract class AbstractUVCCameraHandler extends Handler { +public abstract class AbstractUVCCameraHandler extends Handler { private static final boolean DEBUG = true; // TODO set false on release private static final String TAG = "AbsUVCCameraHandler"; @@ -60,6 +60,12 @@ abstract class AbstractUVCCameraHandler extends Handler { public void onError(final Exception e); } + public static OnEncodeResultListener mListener; + + public interface OnEncodeResultListener{ + void onEncodeResult(byte[] data, int offset, int length, long timestamp, int type); + } + private static final int MSG_OPEN = 0; private static final int MSG_CLOSE = 1; private static final int MSG_PREVIEW_START = 2; @@ -188,7 +194,8 @@ abstract class AbstractUVCCameraHandler extends Handler { } // 开始录制 - public void startRecording(final String path) { + public void startRecording(final String path, OnEncodeResultListener listener) { + AbstractUVCCameraHandler.mListener = listener; checkReleased(); // sendEmptyMessage(MSG_CAPTURE_START); sendMessage(obtainMessage(MSG_CAPTURE_START, path)); @@ -318,7 +325,7 @@ abstract class AbstractUVCCameraHandler extends Handler { } } - static final class CameraThread extends Thread { + public static final class CameraThread extends Thread { private static final String TAG_THREAD = "CameraThread"; private final Object mSync = new Object(); private final Class mHandlerClass; @@ -536,6 +543,7 @@ abstract class AbstractUVCCameraHandler extends Handler { MediaVideoBufferEncoder videoEncoder = null; switch (mEncoderType) { case 1: // for video capturing using MediaVideoEncoder + // 开启视频编码线程 new MediaVideoEncoder(muxer,getWidth(), getHeight(), mMediaEncoderListener); break; case 2: // for video capturing using MediaVideoBufferEncoder @@ -546,6 +554,7 @@ abstract class AbstractUVCCameraHandler extends Handler { new MediaSurfaceEncoder(muxer, getWidth(), getHeight(), mMediaEncoderListener); break; } + // 开启音频编码线程 if (true) { // for audio capturing new MediaAudioEncoder(muxer, mMediaEncoderListener); @@ -684,6 +693,14 @@ abstract class AbstractUVCCameraHandler extends Handler { Log.e(TAG, "onPrepared:", e); } } + + @Override + public void onEncodeResult(byte[] data, int offset, int length, long timestamp, int type) { + if(mListener != null){ + mListener.onEncodeResult(data, offset, length, timestamp, type); + } + } + }; private void loadShutterSound(final Context context) { diff --git a/libusbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java b/libusbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java index 821b5b6..3246364 100644 --- a/libusbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java +++ b/libusbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java @@ -41,7 +41,7 @@ public class MediaAudioEncoder extends MediaEncoder implements IAudioEncoder { private static final String TAG = "MediaAudioEncoder"; private static final String MIME_TYPE = "audio/mp4a-latm"; - private static final int SAMPLE_RATE = 44100; // 44.1[KHz] is only setting guaranteed to be available on all devices. + public static final int SAMPLE_RATE = 44100; // 44.1[KHz] is only setting guaranteed to be available on all devices. private static final int BIT_RATE = 64000; public static final int SAMPLES_PER_FRAME = 1024; // AAC, bytes/frame/channel public static final int FRAMES_PER_BUFFER = 25; // AAC, frame/buffer/sec @@ -192,6 +192,7 @@ public class MediaAudioEncoder extends MediaEncoder implements IAudioEncoder { try { wait(50); } catch (final InterruptedException e) { + } } } diff --git a/libusbcamera/src/main/java/com/serenegiant/usb/encoder/MediaEncoder.java b/libusbcamera/src/main/java/com/serenegiant/usb/encoder/MediaEncoder.java index 585e46e..6debca5 100644 --- a/libusbcamera/src/main/java/com/serenegiant/usb/encoder/MediaEncoder.java +++ b/libusbcamera/src/main/java/com/serenegiant/usb/encoder/MediaEncoder.java @@ -34,6 +34,8 @@ import java.nio.ByteBuffer; public abstract class MediaEncoder implements Runnable { private static final boolean DEBUG = true; // TODO set false on release private static final String TAG = "MediaEncoder"; + public static final int TYPE_VIDEO = 0; // 视频数据 + public static final int TYPE_AUDIO = 1; // 音频数据 protected static final int TIMEOUT_USEC = 10000; // 10[msec] protected static final int MSG_FRAME_AVAILABLE = 1; @@ -42,6 +44,9 @@ public abstract class MediaEncoder implements Runnable { public interface MediaEncoderListener { public void onPrepared(MediaEncoder encoder); public void onStopped(MediaEncoder encoder); + // 音频或视频流,type=0为视频,type=1为音频 + void onEncodeResult(byte[] data, int offset, + int length, long timestamp, int type); } protected final Object mSync = new Object(); @@ -84,6 +89,27 @@ public abstract class MediaEncoder implements Runnable { protected final MediaEncoderListener mListener; + /** + * There are 13 supported frequencies by ADTS. + **/ + public static final int[] AUDIO_SAMPLING_RATES = { 96000, // 0 + 88200, // 1 + 64000, // 2 + 48000, // 3 + 44100, // 4 + 32000, // 5 + 24000, // 6 + 22050, // 7 + 16000, // 8 + 12000, // 9 + 11025, // 10 + 8000, // 11 + 7350, // 12 + -1, // 13 + -1, // 14 + -1, // 15 + }; + public MediaEncoder(final MediaMuxerWrapper muxer, final MediaEncoderListener listener) { if (listener == null) throw new NullPointerException("MediaEncoderListener is null"); if (muxer == null) throw new NullPointerException("MediaMuxerWrapper is null"); @@ -349,6 +375,10 @@ public abstract class MediaEncoder implements Runnable { Log.w(TAG, "muxer is unexpectedly null"); return; } + byte[] mPpsSps = new byte[0]; + byte[] h264 = new byte[640 * 480]; + ByteBuffer mBuffer = ByteBuffer.allocate(10240); + LOOP: while (mIsCapturing) { // get encoded data with maximum timeout duration of TIMEOUT_USEC(=10[msec]) encoderStatus = mMediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC); @@ -416,6 +446,58 @@ LOOP: while (mIsCapturing) { mBufferInfo.presentationTimeUs = getPTSUs(); muxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo); prevOutputPTSUs = mBufferInfo.presentationTimeUs; + + // 推流,获取h.264数据流 +// if(mListener != null){ +// byte[] temp = new byte[mBufferInfo.size]; +// encodedData.get(temp); +// mListener.onEncodeResult(temp, 0,mBufferInfo.size, mBufferInfo.presentationTimeUs / 1000,TYPE_VIDEO); +// } + // 根据mBufferInfo.size来判断音视频 + // > 1000,视频;< 1000,音频 + if(mBufferInfo.size > 1000){ + boolean sync = false; + if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {// sps + sync = (mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; + if (!sync) { + byte[] temp = new byte[mBufferInfo.size]; + encodedData.get(temp); + mPpsSps = temp; + mMediaCodec.releaseOutputBuffer(encoderStatus, false); + continue; + } else { + mPpsSps = new byte[0]; + } + } + sync |= (mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; + int len = mPpsSps.length + mBufferInfo.size; + if (len > h264.length) { + h264 = new byte[len]; + } + if (sync) { + System.arraycopy(mPpsSps, 0, h264, 0, mPpsSps.length); + encodedData.get(h264, mPpsSps.length, mBufferInfo.size); + + if(mListener != null){ + mListener.onEncodeResult(h264, 0,mPpsSps.length + mBufferInfo.size, mBufferInfo.presentationTimeUs / 1000,TYPE_VIDEO); + } + } else { + encodedData.get(h264, 0, mBufferInfo.size); + if(mListener != null){ + mListener.onEncodeResult(h264, 0,mBufferInfo.size, mBufferInfo.presentationTimeUs / 1000,TYPE_VIDEO); + } + } + } else { + mBuffer.clear(); + encodedData.get(mBuffer.array(), 7, mBufferInfo.size); + encodedData.clear(); + mBuffer.position(7 + mBufferInfo.size); + addADTStoPacket(mBuffer.array(), mBufferInfo.size + 7); + mBuffer.flip(); + if(mListener != null){ + mListener.onEncodeResult(mBuffer.array(),0, mBufferInfo.size + 7, mBufferInfo.presentationTimeUs / 1000,TYPE_AUDIO); + } + } } // return buffer to encoder mMediaCodec.releaseOutputBuffer(encoderStatus, false); @@ -428,6 +510,27 @@ LOOP: while (mIsCapturing) { } } + private void addADTStoPacket(byte[] packet, int packetLen) { + packet[0] = (byte) 0xFF; + packet[1] = (byte) 0xF1; + packet[2] = (byte) (((2 - 1) << 6) + (getSamplingRateIndex() << 2) + (1 >> 2)); + packet[3] = (byte) (((1 & 3) << 6) + (packetLen >> 11)); + packet[4] = (byte) ((packetLen & 0x7FF) >> 3); + packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F); + packet[6] = (byte) 0xFC; + } + + private int getSamplingRateIndex(){ + int mSamplingRateIndex = -1; + for (int i=0;i < AUDIO_SAMPLING_RATES.length; i++) { + if (AUDIO_SAMPLING_RATES[i] == MediaAudioEncoder.SAMPLE_RATE) { + mSamplingRateIndex = i; + break; + } + } + return mSamplingRateIndex; + } + /** * previous presentationTimeUs for writing */