Browse Source

支持边推流,边本地录制

main v1.0.0
jiangdongguo 7 years ago
parent
commit
0b2ea3ac80
  1. 83
      app/src/main/java/com/jiangdg/usbcamera/view/USBCameraActivity.java
  2. 51
      libusbcamera/src/main/java/com/jiangdg/usbcamera/USBCameraManager.java
  3. 23
      libusbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java
  4. 3
      libusbcamera/src/main/java/com/serenegiant/usb/encoder/MediaAudioEncoder.java
  5. 103
      libusbcamera/src/main/java/com/serenegiant/usb/encoder/MediaEncoder.java

83
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.hardware.usb.UsbDevice;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.Toast; import android.widget.Toast;
@ -12,8 +16,14 @@ import com.jiangdg.usbcamera.R;
import com.jiangdg.usbcamera.USBCameraManager; import com.jiangdg.usbcamera.USBCameraManager;
import com.serenegiant.usb.CameraDialog; import com.serenegiant.usb.CameraDialog;
import com.serenegiant.usb.USBMonitor; import com.serenegiant.usb.USBMonitor;
import com.serenegiant.usb.common.AbstractUVCCameraHandler;
import com.serenegiant.usb.widget.CameraViewInterface; 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.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import butterknife.OnClick; import butterknife.OnClick;
@ -35,16 +45,32 @@ public class USBCameraActivity extends AppCompatActivity implements CameraDialog
private USBCameraManager mUSBManager; private USBCameraManager mUSBManager;
private CameraViewInterface mUVCCameraView; private CameraViewInterface mUVCCameraView;
private boolean isRequest;
// USB设备监听器 // USB设备监听器
private USBCameraManager.OnMyDevConnectListener listener = new USBCameraManager.OnMyDevConnectListener() { private USBCameraManager.OnMyDevConnectListener listener = new USBCameraManager.OnMyDevConnectListener() {
@Override @Override
public void onAttachDev(UsbDevice device) { 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 @Override
public void onDettachDev(UsbDevice device) { public void onDettachDev(UsbDevice device) {
showShortMsg(device.getDeviceName()+"已拨出"); if(isRequest){
isRequest = false;
mUSBManager.closeCamera();
showShortMsg(device.getDeviceName()+"已拨出");
}
} }
@Override @Override
@ -57,6 +83,7 @@ public class USBCameraActivity extends AppCompatActivity implements CameraDialog
// 处理与设备断开后的逻辑 // 处理与设备断开后的逻辑
} }
}; };
private FileOutputStream fos;
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -101,15 +128,14 @@ public class USBCameraActivity extends AppCompatActivity implements CameraDialog
switch (vId) { switch (vId) {
// 开启或关闭Camera // 开启或关闭Camera
case R.id.camera_view: case R.id.camera_view:
if(mUSBManager != null){ // if(mUSBManager != null){
boolean isOpened = mUSBManager.isCameraOpened(); // boolean isOpened = mUSBManager.isCameraOpened();
if(! isOpened){ // if(! isOpened){
CameraDialog.showDialog(USBCameraActivity.this); // CameraDialog.showDialog(USBCameraActivity.this);
}else { // }else {
mUSBManager.closeCamera(); // mUSBManager.closeCamera();
} // }
} // }
break; break;
case R.id.btn_capture_pic: case R.id.btn_capture_pic:
if(mUSBManager == null || ! mUSBManager.isCameraOpened()){ if(mUSBManager == null || ! mUSBManager.isCameraOpened()){
@ -127,15 +153,46 @@ public class USBCameraActivity extends AppCompatActivity implements CameraDialog
showShortMsg("录制异常,摄像头未开启"); showShortMsg("录制异常,摄像头未开启");
return; 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()){ if(! mUSBManager.isRecording()){
String videoPath = USBCameraManager.ROOT_PATH+System.currentTimeMillis() String videoPath = USBCameraManager.ROOT_PATH+System.currentTimeMillis()
+USBCameraManager.SUFFIX_MP4; +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("正在录制"); mBtnRecord.setText("正在录制");
} else { } else {
mUSBManager.stopRecording(); mUSBManager.stopRecording();
try {
if(fos != null){
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
mBtnRecord.setText("开始录制"); mBtnRecord.setText("开始录制");
} }
break; break;

51
libusbcamera/src/main/java/com/jiangdg/usbcamera/USBCameraManager.java

@ -1,16 +1,23 @@
package com.jiangdg.usbcamera; package com.jiangdg.usbcamera;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.os.Environment; import android.os.Environment;
import com.jiangdg.libusbcamera.R;
import com.serenegiant.usb.CameraDialog; import com.serenegiant.usb.CameraDialog;
import com.serenegiant.usb.DeviceFilter;
import com.serenegiant.usb.USBMonitor; import com.serenegiant.usb.USBMonitor;
import com.serenegiant.usb.common.AbstractUVCCameraHandler;
import com.serenegiant.usb.common.UVCCameraHandler; import com.serenegiant.usb.common.UVCCameraHandler;
import com.serenegiant.usb.widget.CameraViewInterface; import com.serenegiant.usb.widget.CameraViewInterface;
import java.io.File; import java.io.File;
import java.util.List;
import static android.R.attr.filter;
/**USB摄像头工具类 /**USB摄像头工具类
* *
@ -35,6 +42,8 @@ public class USBCameraManager{
// Camera业务逻辑处理 // Camera业务逻辑处理
private UVCCameraHandler mCameraHandler; private UVCCameraHandler mCameraHandler;
private Context mContext;
private USBCameraManager(){} private USBCameraManager(){}
public static USBCameraManager getInstance(){ public static USBCameraManager getInstance(){
@ -60,6 +69,8 @@ public class USBCameraManager{
public void init(Activity activity, final CameraViewInterface cameraView, final OnMyDevConnectListener listener){ public void init(Activity activity, final CameraViewInterface cameraView, final OnMyDevConnectListener listener){
if(cameraView == null) if(cameraView == null)
throw new NullPointerException("CameraViewInterface cannot be null!"); throw new NullPointerException("CameraViewInterface cannot be null!");
mContext = activity.getApplicationContext();
mUSBMonitor = new USBMonitor(activity.getApplicationContext(), new USBMonitor.OnDeviceConnectListener() { mUSBMonitor = new USBMonitor(activity.getApplicationContext(), new USBMonitor.OnDeviceConnectListener() {
// 当检测到USB设备,被回调 // 当检测到USB设备,被回调
@Override @Override
@ -128,6 +139,40 @@ public class USBCameraManager{
} }
} }
/**
* 请求开启第index USB摄像头
*/
public void requestPermission(int index){
List<UsbDevice> 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<UsbDevice> devList = getUsbDeviceList();
if(devList==null || devList.size() ==0){
return 0;
}
return devList.size();
}
private List<UsbDevice> getUsbDeviceList(){
List<DeviceFilter> 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()){ if(mCameraHandler != null && ! isRecording()){
mCameraHandler.startRecording(videoPath); mCameraHandler.startRecording(videoPath,listener);
} }
} }
@ -169,6 +214,8 @@ public class USBCameraManager{
* 释放资源 * 释放资源
* */ * */
public void release(){ public void release(){
// 关闭摄像头
closeCamera();
//释放CameraHandler占用的相关资源 //释放CameraHandler占用的相关资源
if(mCameraHandler != null){ if(mCameraHandler != null){
mCameraHandler.release(); mCameraHandler.release();

23
libusbcamera/src/main/java/com/serenegiant/usb/common/AbstractUVCCameraHandler.java

@ -45,7 +45,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
* Camera业务处理抽象类 * 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 boolean DEBUG = true; // TODO set false on release
private static final String TAG = "AbsUVCCameraHandler"; private static final String TAG = "AbsUVCCameraHandler";
@ -60,6 +60,12 @@ abstract class AbstractUVCCameraHandler extends Handler {
public void onError(final Exception e); 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_OPEN = 0;
private static final int MSG_CLOSE = 1; private static final int MSG_CLOSE = 1;
private static final int MSG_PREVIEW_START = 2; 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(); checkReleased();
// sendEmptyMessage(MSG_CAPTURE_START); // sendEmptyMessage(MSG_CAPTURE_START);
sendMessage(obtainMessage(MSG_CAPTURE_START, path)); 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 static final String TAG_THREAD = "CameraThread";
private final Object mSync = new Object(); private final Object mSync = new Object();
private final Class<? extends AbstractUVCCameraHandler> mHandlerClass; private final Class<? extends AbstractUVCCameraHandler> mHandlerClass;
@ -536,6 +543,7 @@ abstract class AbstractUVCCameraHandler extends Handler {
MediaVideoBufferEncoder videoEncoder = null; MediaVideoBufferEncoder videoEncoder = null;
switch (mEncoderType) { switch (mEncoderType) {
case 1: // for video capturing using MediaVideoEncoder case 1: // for video capturing using MediaVideoEncoder
// 开启视频编码线程
new MediaVideoEncoder(muxer,getWidth(), getHeight(), mMediaEncoderListener); new MediaVideoEncoder(muxer,getWidth(), getHeight(), mMediaEncoderListener);
break; break;
case 2: // for video capturing using MediaVideoBufferEncoder case 2: // for video capturing using MediaVideoBufferEncoder
@ -546,6 +554,7 @@ abstract class AbstractUVCCameraHandler extends Handler {
new MediaSurfaceEncoder(muxer, getWidth(), getHeight(), mMediaEncoderListener); new MediaSurfaceEncoder(muxer, getWidth(), getHeight(), mMediaEncoderListener);
break; break;
} }
// 开启音频编码线程
if (true) { if (true) {
// for audio capturing // for audio capturing
new MediaAudioEncoder(muxer, mMediaEncoderListener); new MediaAudioEncoder(muxer, mMediaEncoderListener);
@ -684,6 +693,14 @@ abstract class AbstractUVCCameraHandler extends Handler {
Log.e(TAG, "onPrepared:", e); 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) { private void loadShutterSound(final Context context) {

3
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 TAG = "MediaAudioEncoder";
private static final String MIME_TYPE = "audio/mp4a-latm"; 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; private static final int BIT_RATE = 64000;
public static final int SAMPLES_PER_FRAME = 1024; // AAC, bytes/frame/channel public static final int SAMPLES_PER_FRAME = 1024; // AAC, bytes/frame/channel
public static final int FRAMES_PER_BUFFER = 25; // AAC, frame/buffer/sec public static final int FRAMES_PER_BUFFER = 25; // AAC, frame/buffer/sec
@ -192,6 +192,7 @@ public class MediaAudioEncoder extends MediaEncoder implements IAudioEncoder {
try { try {
wait(50); wait(50);
} catch (final InterruptedException e) { } catch (final InterruptedException e) {
} }
} }
} }

103
libusbcamera/src/main/java/com/serenegiant/usb/encoder/MediaEncoder.java

@ -34,6 +34,8 @@ import java.nio.ByteBuffer;
public abstract class MediaEncoder implements Runnable { public abstract class MediaEncoder implements Runnable {
private static final boolean DEBUG = true; // TODO set false on release private static final boolean DEBUG = true; // TODO set false on release
private static final String TAG = "MediaEncoder"; 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 TIMEOUT_USEC = 10000; // 10[msec]
protected static final int MSG_FRAME_AVAILABLE = 1; protected static final int MSG_FRAME_AVAILABLE = 1;
@ -42,6 +44,9 @@ public abstract class MediaEncoder implements Runnable {
public interface MediaEncoderListener { public interface MediaEncoderListener {
public void onPrepared(MediaEncoder encoder); public void onPrepared(MediaEncoder encoder);
public void onStopped(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(); protected final Object mSync = new Object();
@ -84,6 +89,27 @@ public abstract class MediaEncoder implements Runnable {
protected final MediaEncoderListener mListener; 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) { public MediaEncoder(final MediaMuxerWrapper muxer, final MediaEncoderListener listener) {
if (listener == null) throw new NullPointerException("MediaEncoderListener is null"); if (listener == null) throw new NullPointerException("MediaEncoderListener is null");
if (muxer == null) throw new NullPointerException("MediaMuxerWrapper 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"); Log.w(TAG, "muxer is unexpectedly null");
return; return;
} }
byte[] mPpsSps = new byte[0];
byte[] h264 = new byte[640 * 480];
ByteBuffer mBuffer = ByteBuffer.allocate(10240);
LOOP: while (mIsCapturing) { LOOP: while (mIsCapturing) {
// get encoded data with maximum timeout duration of TIMEOUT_USEC(=10[msec]) // get encoded data with maximum timeout duration of TIMEOUT_USEC(=10[msec])
encoderStatus = mMediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC); encoderStatus = mMediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
@ -416,6 +446,58 @@ LOOP: while (mIsCapturing) {
mBufferInfo.presentationTimeUs = getPTSUs(); mBufferInfo.presentationTimeUs = getPTSUs();
muxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo); muxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo);
prevOutputPTSUs = mBufferInfo.presentationTimeUs; 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 // return buffer to encoder
mMediaCodec.releaseOutputBuffer(encoderStatus, false); 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 * previous presentationTimeUs for writing
*/ */

Loading…
Cancel
Save