H.264(MediaCodec) + UDP + VLC
若不进行UDP发送,则编码camera preview data数据、然后解码显示(绑定surfaceview的surface)
若开启UDP推送线程,则编码camera preview data数据后,编码数据放入UDP发送列表,
让UDP线程去列表获取数据、发送给VLC端进行播放。
编解码器,类型为video/avc(H.264)
VLC:
网络播放地址(打开网络串流,地址栏输入): udp://@:5000
//REMOTE_HOST= “192.168.10.113”; // the terminal ip address of VLC play,根据自己使用情况更改
//REMOTE_HOST_PORT = 5000; // VLC 监听端口,可更改
Camera camera = null;
private UdpSendTask netSendTask;
private BufferedOutputStream outputStream;
private Camera getCamera(int cameraType) {
Camera camera = null;
try {
camera = Camera.open(Camera.getNumberOfCameras()-1);
} catch (Exception e) {
e.printStackTrace();
}
return camera; // returns null if camera is unavailable
}
private void openCamera(SurfaceHolder holder) {
try {
camera = getCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
} catch (Exception e) {
camera = null;
e.printStackTrace();
}
if(camera != null){
camera.setDisplayOrientation(90);
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(width, height);
parameters.setFlashMode("off");
parameters.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
parameters.setPreviewFormat(ImageFormat.YV12); // ³£Óøñʽ£ºNV21 / YV12
parameters.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);
camera.setParameters(parameters);
buf = new byte[width*height*3/2];
camera.addCallbackBuffer(buf);
camera.setPreviewCallbackWithBuffer(this);
List<int[]> fpsRange = parameters.getSupportedPreviewFpsRange();
for (int[] temp3 : fpsRange) {
System.out.println(Arrays.toString(temp3));
}
parameters.setPreviewFpsRange(15000, 15000);
camera.startPreview();
}
}
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
// TODO Auto-generated method stub
onFrame(data, data.length); //调用MediaCodec进行H.264编码、解码播放、压栈(为Udp推送存储缓存数据)
camera.addCallbackBuffer(buf);
}
private static int selectColorFormat(MediaCodecInfo codecInfo, String mimeType) {
MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
for (int i = 0; i < capabilities.colorFormats.length; i++) {
int colorFormat = capabilities.colorFormats[i];
if (isRecognizedFormat(colorFormat)) {
Log.i("Encoder", "selectColorFormat = "+colorFormat);
return colorFormat;
}
}
Log.e("Encoder","couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType);
return 0;
}
/**
* Returns true if this is a color format that this test code understands (i.e. we know how
* to read and generate frames in this format).
*/
private static boolean isRecognizedFormat(int colorFormat) {
switch (colorFormat) {
// these are the formats we know how to handle for this test
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
return true;
default:
return false;
}
}
/**
* Returns the first codec capable of encoding the specified MIME type, or null if no
* match was found.
*/
private static MediaCodecInfo selectCodec(String mimeType) {
int numCodecs = MediaCodecList.getCodecCount();
for (int i = 0; i < numCodecs; i++) {
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
if (!codecInfo.isEncoder()) {
continue;
}
String[] types = codecInfo.getSupportedTypes();
for (int j = 0; j < types.length; j++) {
if (types[j].equalsIgnoreCase(mimeType)) {
Log.i("Encoder", "selectCodec = "+codecInfo.getName());
return codecInfo;
}
}
}
return null;
}
//编码器配置
public void MediaCodecEncodeInit(){
String type = "video/avc";
File f = new File(Environment.getExternalStorageDirectory(), "mediacodec0.264");
if(!f.exists()){
try {
f.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
if(f.delete()){
try {
f.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
try {
//存储为H.264编码文件,测试使用
outputStream = new BufferedOutputStream(new FileOutputStream(f));
Log.i("Encoder", "outputStream initialized");
} catch (Exception e){
e.printStackTrace();
}
//根据type类型选择颜色格式
int colorFormat = selectColorFormat(selectCodec("video/avc"), "video/avc");
mediaCodec0 = MediaCodec.createEncoderByType(type);
mediaCodec = MediaCodec.createEncoderByType(type);
MediaFormat mediaFormat = MediaFormat.createVideoFormat(type, width, height);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000);//125kbps
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15); // 帧率
//mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
// MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar); //COLOR_FormatYUV420Planar
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); //I 帧间隔时间
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();
mediaCodec0.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec0.start();
}
//解码器配置
public void MediaCodecDecodeInit(){
String type = "video/avc";
mediaCodecd = MediaCodec.createDecoderByType(type);
MediaFormat mediaFormat = MediaFormat.createVideoFormat(type, width, height);
mediaCodecd.configure(mediaFormat, surface, null, 0);
mediaCodecd.start();
}
private static String REMOTE_HOST= "192.168.10.113"; // the terminal of VLC play
private static final short REMOTE_HOST_PORT = 5000; // VLC 监听端口
private static InetAddress address;
private static DatagramSocket socket;
static class UdpSendTask extends Thread{
private ArrayList<ByteBuffer> mList; //Nalu缓存列表
static boolean running;
public void init()
{
try {
socket = new DatagramSocket();
address = InetAddress.getByName(REMOTE_HOST); //REMOTE_HOST => VLC端的IP地址
mList = new ArrayList<ByteBuffer>();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
running = true;
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
public void end()
{
running = false;
runFlag = false;
}
public void pushBuf(byte[] buf,int len)
{
ByteBuffer buffer = ByteBuffer.allocate(len);
buffer.put(buf,0,len);
mList.add(buffer);
}
@Override
public void run() {
while(running){
if(mList.size() <= 0){
try {
Thread.sleep(70);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
while(mList.size() > 0){
ByteBuffer sendBuf = mList.get(0);
try {
DatagramPacket packet=new DatagramPacket(sendBuf.array(),sendBuf.capacity(), address,REMOTE_HOST_PORT);
socket.send(packet);
} catch (Throwable t) {
t.printStackTrace();
}
mList.remove(0);
if(!running){
break;
}
}
}
mList.clear();
}
}
//解码h.264编码数据
public void onFrame0(byte[] buf, int length) {
ByteBuffer[] inputBuffers = mediaCodecd.getInputBuffers();
int inputBufferIndex = mediaCodecd.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(buf, 0, length);
mediaCodecd.queueInputBuffer(inputBufferIndex, 0, length,
mCount * 1000000 / FRAME_RATE, 0); // 加入pts,以便正确渲染surface、显示
mCount++;
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodecd.dequeueOutputBuffer(bufferInfo,0);
while (outputBufferIndex >= 0) {
mediaCodecd.releaseOutputBuffer(outputBufferIndex, true);
outputBufferIndex = mediaCodecd.dequeueOutputBuffer(bufferInfo, 0);
}
}
//yv12 =》 yuv420p : yvu -> yuv
private void swapYV12toI420(byte[] yv12bytes, byte[] i420bytes, int width, int height)
{
System.arraycopy(yv12bytes, 0, i420bytes, 0,width*height);
System.arraycopy(yv12bytes, width*height+width*height/4, i420bytes, width*height,width*height/4);
System.arraycopy(yv12bytes, width*height, i420bytes, width*height+width*height/4,width*height/4);
}
//编码camera preview data
public void onFrame(byte[] buf, int length) {
swapYV12toI420(buf, h264, width, height); // H.264编码器只支持YUV视频格式输入
ByteBuffer[] inputBuffers = mediaCodec0.getInputBuffers();
ByteBuffer[] outputBuffers = mediaCodec0.getOutputBuffers();
int inputBufferIndex = mediaCodec0.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(h264, 0, length);
mediaCodec0.queueInputBuffer(inputBufferIndex, 0, length, 0, 0);
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec0.dequeueOutputBuffer(bufferInfo,0);
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);
/*
try {
outputStream.write(outData, 0, outData.length); // write into h264 file
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
*/
if(!runFlag){
onFrame0(outData, 0, outData.length, flag); // 调用解码、本地显示
if(firstFlag){
Log.i("Encoder", " onFrame pps sps ");
int len = outData.length;
for (int ix = 0; ix < len; ++ix) {
System.out.printf("%02x ", outData[ix]);
}
System.out.println("\n----------");
//存储编码器输出的sps pps参数(编码器一般只输出一次)
System.arraycopy(outData, 0, outData0, 0, outData.length);
firstFlag = false;
}
}else{
if(netSendTask != null){
if(UdpSendTask.running){
if(secondFlag){
netSendTask.pushBuf(outData0, outData0.length); // 保持
secondFlag = false;
}
if(outData[4] == 0x65){
threeFlag = true;
}
if(threeFlag){
//把编码后的nalu数据推进到UDP发送列表
netSendTask.pushBuf(outData, outData.length);
}
}
}
}
mediaCodec0.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodec0.dequeueOutputBuffer(bufferInfo, 0);
}
}