背景:

  由于,项目需要,需要进行视频通信,把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() {@Overridepublic 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 }

视频编码类Encoder

其中使用到了,接口用于,把采集和编码后的数据,往外部传递,通过线程提交到服务端。或者通过本地解码显示,查看,编码解码时间差。

通过使用 ArrayBlockingQueue<byte[]> H264Queue = new ArrayBlockingQueue<byte[]>(10); 队列,对接口提交数据,进行暂时保存,在后台对数据,进行解码或提交到服务端。

  APP2:接入服务端,然后从I帧数据开始拿数据,(且数据是最新的I帧开始保存的数据)。同时需要把,之前采集得到的时间点传给:

MediaCodec 对象的 queueInputBuffer 方法的时间戳参数(第四个)。

服务端:一帧一帧接收APP1传入数据,对I帧开始的数据进行记录,同时对非I帧开始的数据,进行丢弃。一次只保存一帧内容。读取数据,并且移除已经添加数据,循环发送给APP2

public class VideoDecoder {private Thread mDecodeThread;private MediaCodec mCodec;private boolean mStopFlag = false;private int Video_Width = 640;private int Video_Height = 480;private int FrameRate = 25;private Boolean isUsePpsAndSps = false;private ReceiveVideoThread runThread = null;public VideoDecoder(String ip, int port, byte type, int roomId){runThread = new ReceiveVideoThread(ip, port, type, roomId);new Thread(runThread).start();}public void InitReadData(Surface surface){try {//通过多媒体格式名创建一个可用的解码器mCodec = MediaCodec.createDecoderByType("video/avc");} catch (IOException e) {e.printStackTrace();}//初始化编码器final MediaFormat mediaformat = MediaFormat.createVideoFormat("video/avc", Video_Width, Video_Height);//设置帧率
        mediaformat.setInteger(MediaFormat.KEY_FRAME_RATE, FrameRate);//https://developer.android.com/reference/android/media/MediaFormat.html#KEY_MAX_INPUT_SIZE//设置配置参数,参数介绍 :// format   如果为解码器,此处表示输入数据的格式;如果为编码器,此处表示输出数据的格式。//surface   指定一个surface,可用作decode的输出渲染。//crypto    如果需要给媒体数据加密,此处指定一个crypto类.//   flags  如果正在配置的对象是用作编码器,此处加上CONFIGURE_FLAG_ENCODE 标签。mCodec.configure(mediaformat, surface, null, 0);startDecodingThread();}private void startDecodingThread() {mCodec.start();mDecodeThread = new Thread(new decodeH264Thread());mDecodeThread.start();}@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)private class decodeH264Thread implements Runnable {@Overridepublic void run() {try {// saveDataLoop();
                decodeLoop_New();} catch (Exception e) {e.printStackTrace();}}private void decodeLoop_New() {// 存放目标文件的数据ByteBuffer[] inputBuffers = mCodec.getInputBuffers();// 解码后的数据,包含每一个buffer的元数据信息,例如偏差,在相关解码器中有效的数据大小MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();long timeoutUs = 1000;byte[] marker0 = new byte[]{0, 0, 0, 1};byte[] dummyFrame = new byte[]{0x00, 0x00, 0x01, 0x20};byte[] streamBuffer = null;while (true) {if(runThread.H264Queue.size() > 0){streamBuffer = runThread.H264Queue.poll();}else{try {Thread.sleep(20);}catch (Exception ex){}continue;}byte[] time_data = new byte[8];System.arraycopy(streamBuffer, 0, time_data, 0, 8);long pts = IntBytes.bytesToLong(time_data);byte[] video_data = new byte[streamBuffer.length - 8];System.arraycopy(streamBuffer, 8, video_data, 0, video_data.length);streamBuffer = video_data;Logs.i("得到 streamBuffer " + streamBuffer.length + " pts " + pts);int bytes_cnt = 0;mStopFlag = false;while (mStopFlag == false) {bytes_cnt = streamBuffer.length;if (bytes_cnt == 0) {streamBuffer = dummyFrame;}int startIndex = 0;int remaining = bytes_cnt;while (true) {if (remaining == 0 || startIndex >= remaining) {break;}int nextFrameStart = KMPMatch(marker0, streamBuffer, startIndex + 2, remaining);if (nextFrameStart == -1) {nextFrameStart = remaining;} else {}int inIndex = mCodec.dequeueInputBuffer(timeoutUs);if (inIndex >= 0) {ByteBuffer byteBuffer = inputBuffers[inIndex];byteBuffer.clear();byteBuffer.put(streamBuffer, startIndex, nextFrameStart - startIndex);//在给指定Index的inputbuffer[]填充数据后,调用这个函数把数据传给解码器mCodec.queueInputBuffer(inIndex, 0, nextFrameStart - startIndex, pts, 0);startIndex = nextFrameStart;} else {continue;}int outIndex = mCodec.dequeueOutputBuffer(info, timeoutUs);if (outIndex >= 0) {//帧控制是不在这种情况下工作,因为没有PTS H264是可用的/*while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}*/boolean doRender = (info.size != 0);//对outputbuffer的处理完后,调用这个函数把buffer重新返回给codec类。// TODO:添加处理,保存原始帧数据if (doRender) {Image image = mCodec.getOutputImage(outIndex);if (image != null) {// 通过反射// 发送数据到指定接口byte[] data = getDataFromImage(image, COLOR_FormatNV21);}}mCodec.releaseOutputBuffer(outIndex, doRender);} else {// Log.e(TAG, "bbbb");
                        }}mStopFlag = true;}// Logs.i("处理单帧视频耗时:" + (System.currentTimeMillis() - c_start));
            }}}private static final boolean VERBOSE = false;private static final long DEFAULT_TIMEOUT_US = 10000;private static final int COLOR_FormatI420 = 1;private static final int COLOR_FormatNV21 = 2;private static boolean isImageFormatSupported(Image image) {int format = image.getFormat();switch (format) {case ImageFormat.YUV_420_888:case ImageFormat.NV21:case ImageFormat.YV12:return true;}return false;}@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)private static byte[] getDataFromImage(Image image, int colorFormat) {if (colorFormat != COLOR_FormatI420 && colorFormat != COLOR_FormatNV21) {throw new IllegalArgumentException("only support COLOR_FormatI420 " + "and COLOR_FormatNV21");}if (!isImageFormatSupported(image)) {throw new RuntimeException("can't convert Image to byte array, format " + image.getFormat());}Rect crop = image.getCropRect();int format = image.getFormat();int width = crop.width();int height = crop.height();Image.Plane[] planes = image.getPlanes();byte[] data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];byte[] rowData = new byte[planes[0].getRowStride()];if (VERBOSE) Logs.i("get data from " + planes.length + " planes");int channelOffset = 0;int outputStride = 1;for (int i = 0; i < planes.length; i++) {switch (i) {case 0:channelOffset = 0;outputStride = 1;break;case 1:if (colorFormat == COLOR_FormatI420) {channelOffset = width * height;outputStride = 1;} else if (colorFormat == COLOR_FormatNV21) {channelOffset = width * height + 1;outputStride = 2;}break;case 2:if (colorFormat == COLOR_FormatI420) {channelOffset = (int) (width * height * 1.25);outputStride = 1;} else if (colorFormat == COLOR_FormatNV21) {channelOffset = width * height;outputStride = 2;}break;}ByteBuffer buffer = planes[i].getBuffer();int rowStride = planes[i].getRowStride();int pixelStride = planes[i].getPixelStride();if (VERBOSE) {Logs.i("pixelStride " + pixelStride);Logs.i("rowStride " + rowStride);Logs.i("width " + width);Logs.i("height " + height);Logs.i("buffer size " + buffer.remaining());}int shift = (i == 0) ? 0 : 1;int w = width >> shift;int h = height >> shift;buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));for (int row = 0; row < h; row++) {int length;if (pixelStride == 1 && outputStride == 1) {length = w;buffer.get(data, channelOffset, length);channelOffset += length;} else {length = (w - 1) * pixelStride + 1;buffer.get(rowData, 0, length);for (int col = 0; col < w; col++) {data[channelOffset] = rowData[col * pixelStride];channelOffset += outputStride;}}if (row < h - 1) {buffer.position(buffer.position() + rowStride - length);}}if (VERBOSE) Logs.i("Finished reading data from plane " + i);}return data;}private int KMPMatch(byte[] pattern, byte[] bytes, int start, int remain) {try {Thread.sleep(30);} catch (InterruptedException e) {e.printStackTrace();}int[] lsp = computeLspTable(pattern);int j = 0;  // Number of chars matched in patternfor (int i = start; i < remain; i++) {while (j > 0 && bytes[i] != pattern[j]) {// Fall back in the patternj = lsp[j - 1];  // Strictly decreasing
            }if (bytes[i] == pattern[j]) {// Next char matched, increment positionj++;if (j == pattern.length)return i - (j - 1);}}return -1;  // Not found
    }private int[] computeLspTable(byte[] pattern) {int[] lsp = new int[pattern.length];lsp[0] = 0;  // Base casefor (int i = 1; i < pattern.length; i++) {// Start by assuming we're extending the previous LSPint j = lsp[i - 1];while (j > 0 && pattern[i] != pattern[j])j = lsp[j - 1];if (pattern[i] == pattern[j])j++;lsp[i] = j;}return lsp;}public void StopDecode() {if(runThread != null){runThread.StopReceive();}}
}

视频解码类Decoder

总结:

  通过对视频的处理,学习到了,一些处理视频的细节点。同时加深了,依赖导致在实际项目中的使用。to android.

Android 视频通信,低延时解决方案相关推荐

  1. 【分享】性能比肩美拍秒拍的Android视频录制编辑特效解决方案【1】

    前言 本人接触Android的时间有限,如果您有更好的解决方案,欢迎吐槽. 众所周知,Android平台开发分为Java层和C++层,即Android SDK和Android NDK.常规产品功能只需 ...

  2. android视频通信和web端,探讨用webrtc在手机和浏览器之间实现音视频实时通信的实施环境...

    探讨用webrtc在手机和浏览器之间实现音视频实时通信的实施环境 Walker.Xu product/develop flow: 技术需求: 任务拆解: 1.android客户端 2.前端js网页客户 ...

  3. 【分享】性能比肩美拍秒拍的Android视频录制编辑特效解决方案【2】

    上一篇文章是初步接触Andorid多媒体开发时,以使用纯开源的方式实现的基础效果,效率上有很大问题.经过半年多的继续学习,解决了其中部分有效率问题的地方. (1)编解码部分 编解码部分之前文章采用的X ...

  4. 可扩展性强且经济高效?RealMedia HD低延时直播方案为你支招

    欢迎光临新一期的Real编码研习社,本期我们有幸请到了RealNetworks媒体解决方案架构师罗强,想了解低延时的直播方案如何能兼具可扩展性强和经济高效的特点,今天这一期一定不要错过哦~ 一.在您看 ...

  5. 低延时直播与RTC融合架构设计③:RTC融合架构设计

    本篇文章中,吴桐将向大家介绍网易云信NRTC融合架构.RTC视频会议场景优化方案以及他个人一些前瞻性的思考和展望. 网易云信NRTC融合架构 NRTC是NetEase Real-Time Commun ...

  6. 超低延时监控视频多终端发布解决方案

    超低延时监控视频多终端发布解决方案 第一章 应用简介 第二章 方案的实现方式 2.1 方案的技术架构 2.2 功能模块构成 第三章 平台的安装和部署 3.1 视频转码工作站的搭建 3.2 流媒体服务器 ...

  7. 海康、大华视频监控在浏览器端无插件低延时播放解决方案

    海康.大华视频监控无插件低延时播放解决方案 第一章 应用简介 第二章 方案的实现方式 2.1 方案的技术架构 2.2 功能模块构成 第三章 平台的安装和部署 3.1 视频转码工作站的搭建 3.2 流媒 ...

  8. 如何搭建低延时、交互式的在线教育平台?(内附视频回放)

    本文由腾讯互动课堂技术负责人缪少豪在LiveVideoStack线上分享中的内容整理而成,详细介绍了腾讯云在线教育互动课堂方案的设计与技术挑战,重点解析了互动白板的实现技术难点与突破. 文 / 缪少豪 ...

  9. 监控摄像头RTSP低延时无插件直播解决方案

    监控摄像头RTSP低延时无插件直播解决方案 第一章 应用简介 当前,视频监控应用场景越来越多,传统的视频监控厂商提供的解决方案需要安装厂商自己的手机APP或PC客户端软件,非常不方便在互联网环境下与第 ...

最新文章

  1. 拉格朗日 SVM KKT
  2. 经典论文复现 | 基于深度卷积网络的图像超分辨率算法
  3. sed、head、grep、tail、EOF
  4. python多进程运行MIC(最大信息系数)
  5. 基于JAVA+SpringMVC+Mybatis+MYSQL的网上书店管理系统
  6. 【我的相册】北方的传统面艺
  7. [转载] C++ std::vector指定位置插入
  8. Android Audio子系统路由策略(三十六)
  9. 鸿蒙冰心有其他途径得到吗,关索除了人遁礼包,还有其他途径能获得吗?
  10. C3P0组件+DbUtils组件实现一个JdbcUtils工具类
  11. 全球最值得模仿的500个网站(扫描版pdf)
  12. Excel VBA宏
  13. 京委本圣经的历史考证
  14. 2021肿瘤早筛行业研究报告
  15. java解析word 波浪线,word页面边框双波浪线
  16. 国产数据库普及风暴有奖征文获奖名单揭晓
  17. “社畜”群体的崛起带来了哪些营销新契机?
  18. python实现分词算法代码
  19. [LLVM教程]LLVM之第一个语言前端
  20. L7 U2 希望与梦想

热门文章

  1. php 封装JavaScript类
  2. python——pandas数据分析(表格处理)工具实现Apriori算法
  3. Nginx开启stub_status模块配置方法_nginx
  4. 数据库设计的6个阶段
  5. SOCK_STREAM和SOCK_DGRAM两种类型的区别
  6. Python--map用法
  7. 2016校招真题编程练习——微信红包(腾讯)
  8. 【三维深度学习】点云上采样网络PU-Net 代码分析
  9. 使用ArrayList对大小写字母的随机打印
  10. DDL 创建与查询数据库