背景:
由于,项目需要,需要进行视频通信,把a的画面,转给b。
运维部署:
APP1:编码摄像头采集的数据,并且发送数据到服务端
APP2:从服务端,拉取数据,并且进行解码显示
服务端:接收APP1提交的数据,发送APP1提交数据到APP2
应用说明:
APP1:camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT);
Camera.Parameters parameters = camera.getParameters(); parameters.setPreviewFormat(ImageFormat.NV21); parameters.setPreviewSize(width, height); // 设置屏幕亮度 parameters.setExposureCompensation(parameters.getMaxExposureCompensation() / 2); camera.setParameters(parameters); camera.setDisplayOrientation(90); camera.setPreviewCallback(new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera camera) {
// 采集视频数据,同时记录采集视频的时间点,解码需要(保证视频连续,流畅,且不花屏需要) stamptime = System.nanoTime(); yuv_data = data; } });
1 public class AvcKeyFrameEncoder { 2 private final static String TAG = "MeidaCodec"; 3 private int TIMEOUT_USEC = 12000; 4 5 private MediaCodec mediaCodec; 6 int m_width; 7 int m_height; 8 int m_framerate; 9 10 public byte[] configbyte; 11 12 //待解码视频缓冲队列,静态成员! 13 public byte[] yuv_data = null; 14 public long stamptime = 0; 15 16 public AvcKeyFrameEncoder(int width, int height, int framerate) { 17 m_width = width; 18 m_height = height; 19 m_framerate = framerate; 20 21 //正常的编码出来是横屏的。因为手机本身采集的数据默认就是横屏的 22 // MediaFormat mediaFormat = MediaFormat.createVideoFormat(mime, width, height); 23 //如果你需要旋转90度或者270度,那么需要把宽和高对调。否则会花屏。因为比如你320 X 240,图像旋转90°之后宽高变成了240 X 320。 24 MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height); 25 mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar); 26 mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000); 27 mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate); // 30 28 mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); 29 try { 30 mediaCodec = MediaCodec.createEncoderByType("video/avc"); 31 } catch (IOException e) { 32 e.printStackTrace(); 33 } 34 35 //配置编码器参数 36 mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 37 38 //启动编码器 39 mediaCodec.start(); 40 } 41 42 public void StopEncoder() { 43 try { 44 mediaCodec.stop(); 45 mediaCodec.release(); 46 } catch (Exception e) { 47 e.printStackTrace(); 48 } 49 } 50 51 public boolean isRuning = false; 52 53 public void StartEncoderThread(final ISaveVideo saveVideo, final ICall callback) { 54 isRuning = true; 55 new Thread(new Runnable() { 56 @Override 57 public void run() { 58 byte[] input = null; 59 long pts = 0; 60 while (isRuning) { 61 // 访问MainActivity用来缓冲待解码数据的队列 62 if(yuv_data == null){ 63 continue; 64 } 65 66 if (yuv_data != null) { 67 //从缓冲队列中取出一帧 68 input = yuv_data; 69 pts = stamptime; 70 yuv_data = null; 71 byte[] yuv420sp = new byte[m_width * m_height * 3 / 2]; 72 73 NV21ToNV12(input, yuv420sp, m_width, m_height); 74 input = yuv420sp; 75 } 76 77 if (input != null) { 78 try { 79 //编码器输入缓冲区 80 ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); 81 82 //编码器输出缓冲区 83 ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers(); 84 int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1); 85 if (inputBufferIndex >= 0) { 86 ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; 87 inputBuffer.clear(); 88 //把转换后的YUV420格式的视频帧放到编码器输入缓冲区中 89 inputBuffer.put(input); 90 mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, pts, 0); 91 } 92 93 MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); 94 int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC); 95 while (outputBufferIndex >= 0) { 96 //Log.i("AvcEncoder", "Get H264 Buffer Success! flag = "+bufferInfo.flags+",pts = "+bufferInfo.presentationTimeUs+""); 97 ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; 98 byte[] outData = new byte[bufferInfo.size]; 99 outputBuffer.get(outData); 100 if (bufferInfo.flags == BUFFER_FLAG_CODEC_CONFIG) { 101 configbyte = new byte[bufferInfo.size]; 102 configbyte = outData; 103 } else if (bufferInfo.flags == BUFFER_FLAG_KEY_FRAME) { 104 byte[] keyframe = new byte[bufferInfo.size + configbyte.length]; 105 System.arraycopy(configbyte, 0, keyframe, 0, configbyte.length); 106 //把编码后的视频帧从编码器输出缓冲区中拷贝出来 107 System.arraycopy(outData, 0, keyframe, configbyte.length, outData.length); 108 109 Logs.i("上传I帧 " + keyframe.length); 110 byte[] send_data = new byte[13 + keyframe.length]; 111 System.arraycopy(new byte[]{0x01}, 0, send_data, 0, 1); 112 System.arraycopy(IntBytes.longToBytes(pts), 0, send_data, 1, 8); 113 System.arraycopy(IntBytes.intToByteArray(keyframe.length), 0, send_data, 9, 4); 114 System.arraycopy(keyframe, 0, send_data, 13, keyframe.length); 115 if(saveVideo != null){ 116 saveVideo.SaveVideoData(send_data); 117 } 118 119 if(callback != null){ 120 callback.callback(keyframe, pts); 121 } 122 } else { 123 byte[] send_data = new byte[13 + outData.length]; 124 System.arraycopy(new byte[]{0x02}, 0, send_data, 0, 1); 125 System.arraycopy(IntBytes.longToBytes(pts), 0, send_data, 1, 8); 126 System.arraycopy(IntBytes.intToByteArray(outData.length), 0, send_data, 9, 4); 127 System.arraycopy(outData, 0, send_data, 13, outData.length); 128 if(saveVideo != null){ 129 saveVideo.SaveVideoData(send_data); 130 } 131 132 if(callback != null){ 133 callback.callback(outData, pts); 134 } 135 } 136 137 mediaCodec.releaseOutputBuffer(outputBufferIndex, false); 138 outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC); 139 } 140 141 } catch (Throwable t) { 142 t.printStackTrace(); 143 break; 144 } 145 } 146 } 147 } 148 }).start(); 149 } 150 151 private void NV21ToNV12(byte[] nv21, byte[] nv12, int width, int height) { 152 if (nv21 == null || nv12 == null) return; 153 int framesize = width * height; 154 int i = 0, j = 0; 155 System.arraycopy(nv21, 0, nv12, 0, framesize); 156 for (i = 0; i < framesize; i++) { 157 nv12[i] = nv21[i]; 158 } 159 160 for (j = 0; j < framesize / 2; j += 2) { 161 nv12[framesize + j - 1] = nv21[j + framesize]; 162 } 163 164 for (j = 0; j < framesize / 2; j += 2) { 165 nv12[framesize + j] = nv21[j + framesize - 1]; 166 } 167 } 168 }