终于可以动笔写这篇文章了。
各种调研之后,终于复现成功了一个版本。情况如下
首先,ffmpeg编译的时候,需要指定NDK的版本,所以就算偷懒用别人的so库,也得保证自己的NDK和编译的so是一致的,之前没有在意这个事情,so库各种乱用,所以怎么调试都有各种各样的问题。(其实好像接近就可能可以,下面参考的这个NDK是13...啪啪啪打脸)
由于公司权限设置,安装什么的太麻烦了。网上下载了一个14e的NDK,然后参考了一个用同样NDK编译的http://blog.csdn.net/a992036795/article/details/53941436(我也只是从时间上推测是一致的,不知道有哪位大神可以告知,通过编译完的,查看一些配置参数)。这个效果是,点击开始保存,然后存到某路径,然后再点击是结束,编码出来的H264是可以播放的。接下去是搞个解码器,读出来,然后播放在surface上。
事实证明,那个编码,在另一台Android上不好使,连软件编码都没有很好的兼容性了。又折腾了几天,终于马上要完成利用MediaCodec编解码,视频聊天的过程了。后续看情况,应该再拾起ffmpeg,毕竟简历已经写做完了,光光有编码应该是不够的。
先介绍利用系统自带的API,实现的过程。
首先系统要求:我记得是4.3以上,才会携带相关的API,
测试环境5.0以及5.1的两种平板(一老一新)
首先是编码部分:流程是一些老掉牙的东西,原理什么的网上也都有。例如http://blog.csdn.net/mybook1122/article/details/60966094。
分三个部分说
第一部分是camera:我用的是camera类,就是,在android写起来,经常被一横线划掉,表明,你又用老掉牙的东西的那个类,贴下源码:
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.view.SurfaceView;
import java.io.IOException;
/**
* camera生成类,采用camera类
* Created by 173.115 on 2017/8/17.
*/
public class MyCamera {
/**这里是调用前置摄像头*/
private final int m_CameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
/**camear*/
private Camera m_Camera;
/**camera显示的控件*/
private SurfaceView m_surfaceView;
/**调用该类的类*/
private VideoCaptureControler m_videoCaptureControler;
public MyCamera(VideoCaptureControler a_videoCaptureControler,SurfaceView a_surfaceView) {
this.m_videoCaptureControler = a_videoCaptureControler;
this.m_surfaceView = a_surfaceView;
InitCamera();
}
/**
* init
*/
public void InitCamera() {
m_Camera = Camera.open(m_CameraId);
try {
/**设置显示的surface*/
m_Camera.setPreviewDisplay(m_surfaceView.getHolder());
// mCamera.setDisplayOrientation(90);
Camera.Parameters parameters = m_Camera.getParameters();
//parameters.setPictureFormat(ImageFormat.YV12);//phone?-
/**选择NV21,有些不一定支持,但是和encoder和decoder息息相关*/
parameters.setPreviewFormat(ImageFormat.NV21);
//parameters.setPreviewFrameRate(30);
//parameters.setPictureSize(WIDTH, LENGTH);
parameters.setPreviewSize(GlobalConstant.VIDEOWIDTH, GlobalConstant.VIDEOHIGHT);
m_Camera.setParameters(parameters);
/**设置90度,否则不正*/
m_Camera.setDisplayOrientation(90);//camera显示角度偏向
m_Camera.setPreviewCallback(m_videoCaptureControler);
m_Camera.addCallbackBuffer(m_videoCaptureControler.getBuf());
m_Camera.setPreviewCallbackWithBuffer(m_videoCaptureControler);
} catch (IOException e) {
e.printStackTrace();
}
m_Camera.startPreview();
}
/**
* close
*/
public void closeCamera() {
if(m_Camera != null){
m_Camera.setPreviewCallback(null);
m_Camera.stopPreview();
m_Camera.release();
m_Camera = null;
}
}
}
几个要素,第一,我用的数前置摄像头,第二,分辨率是640x480(具体和设备支持有关,这个是比较通用的)。第三,我用的竖屏,因此摄像头的东西是有90度偏差的(这里的解决办法是,显示的时候,我简单的调转90,但是实际传的时候,是不掉转的,这个编码部分说)。第四,我是pad,用的NV12,用YV12就不行(好像是解不出来)第五就是设置回调了,这个网上的代码也都有。
第二部分:编码器,也先贴代码:
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.util.Log;
import java.io.IOException;
import java.nio.ByteBuffer;
import client.chatapp.fujisu.com.chat_client.CodecManager.Codec;
/**
* 编码类
* Created by 173.115 on 2017/8/17.
*/
public class Encoder {
private byte[] m_sps;
private byte[] m_pps;
/**编码类*/
private MediaCodec m_Encoder;
/**镜像,旋转后的图像*/
private byte[] m_yuv420;
/**原始*/
private byte[] m_yuv42090;
/**翻转后的*/
private byte[] m_yuv420fan;
private byte[] m_Info = null;
/**帧率*/
private int m_frameRate;
/**比特率*/
private int m_bitRate;
private int m_colorFormat;
/**
* 构造函数
* @param a_frameRate
* @param a_bitRate
*/
public Encoder(int a_frameRate, int a_bitRate) {
this.m_frameRate = a_frameRate;
this.m_bitRate = a_bitRate;
m_yuv420 = new byte[GlobalConstant.VIDEOWIDTH * GlobalConstant.VIDEOHIGHT * 3 / 2];
m_yuv42090 = new byte[GlobalConstant.VIDEOWIDTH * GlobalConstant.VIDEOHIGHT * 3 / 2];
m_yuv420fan = new byte[GlobalConstant.VIDEOWIDTH * GlobalConstant.VIDEOHIGHT * 3 / 2];
/**获取可用的ColorFormat,是编码器设置失败的非常关键的参数*/
Codec[] encoders = CodecManager.findEncodersForMimeType("video/avc");
m_colorFormat = getColorFormat(encoders);
}
public void start()
{
try {
m_Encoder = MediaCodec.createEncoderByType("video/avc");//H264
} catch (IOException e) {
e.printStackTrace();
}
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc",GlobalConstant.VIDEOHIGHT,
GlobalConstant.VIDEOWIDTH);//编码的图像已经是颠倒的了
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, m_bitRate);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, m_frameRate);
//mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,debugger.getEncoderColorFormat());
//mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, encoders[0].formats[0]);
/**最麻烦的参数*/
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, m_colorFormat);
//mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
m_Encoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
m_Encoder.start();
}
private int getColorFormat(Codec[] encoders) {
int m_EncoderColorFormat = 0;
String m_EncoderName;
// Tries available encoders
for (int i = 0; i < encoders.length; i++) {
for (int j = 0; j < encoders[i].formats.length; j++) {
m_EncoderName = encoders[i].name;
m_EncoderColorFormat = encoders[i].formats[j];
try {
// Starts the encoder
/**假装配置,看是否报错*/
configureEncoder(m_EncoderName, m_EncoderColorFormat);
/**配置完后设置基本参数,捕捉错误*/
ByteBuffer[] inputBuffers = m_Encoder.getInputBuffers();
ByteBuffer[] outputBuffers = m_Encoder.getOutputBuffers();
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
int bufferIndex = m_Encoder.dequeueInputBuffer(1000000 / m_frameRate);
int index = m_Encoder.dequeueOutputBuffer(info, 1000000 / m_frameRate);
/**没错就返回*/
return m_EncoderColorFormat;
} catch (Exception e) {
e.printStackTrace();
} finally {
/**anyway,要释放*/
releaseEncoder();
}
}
}
return m_EncoderColorFormat;
}
/**
* 假装配置函数
* @param m_EncoderName
* @param m_EncoderColorFormat
* @throws IOException
*/
private void configureEncoder(String m_EncoderName,int m_EncoderColorFormat) throws IOException {
m_Encoder = MediaCodec.createByCodecName(m_EncoderName);
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc",
GlobalConstant.VIDEOWIDTH, GlobalConstant.VIDEOHIGHT);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, m_bitRate);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, m_frameRate);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
m_EncoderColorFormat);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
m_Encoder.configure(mediaFormat, null, null,
MediaCodec.CONFIGURE_FLAG_ENCODE);
m_Encoder.start();
}
private void releaseEncoder() {
if (m_Encoder != null) {
try {
m_Encoder.stop();
} catch (Exception ignore) {
}
try {
m_Encoder.release();
} catch (Exception ignore) {
}
}
}
public void close() {
try {
m_Encoder.stop();
m_Encoder.release();
m_Encoder = null;
//mDecoder.stop();
//mDecoder.release();
//mDecoder = null;
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 喂给编码器
* @param input
* @param output
* @return
*/
public int offerEncoder(byte[] input, byte[] output) {
int pos = 0;
//swapYV12toI420(input, yuv420, width, height);
/**图像预处理,镜像+旋转*/
nv21ToI420(input, m_yuv42090, GlobalConstant.VIDEOWIDTH, GlobalConstant.VIDEOHIGHT);
FilpY420_horizontal(m_yuv42090,m_yuv420fan, GlobalConstant.VIDEOWIDTH, GlobalConstant.VIDEOHIGHT);
//System.arraycopy(input,input.length,yuv420,0,input.length);
RotateY420_90(m_yuv420fan,m_yuv420, GlobalConstant.VIDEOWIDTH, GlobalConstant.VIDEOHIGHT);
//yuv420 = yuv420rotate.rotateYUV420Degree90(yuv42090,width,height);
//yuv420rotate.rotateYUV240SP(yuv42090,yuv420,width,height);
/**从buffer里取个有效buffer,然后把数据塞进去*/
ByteBuffer[] inputBuffers = m_Encoder.getInputBuffers();
ByteBuffer[] outputBuffers = m_Encoder.getOutputBuffers();
int inputBufferIndex = m_Encoder.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(m_yuv420);
m_Encoder.queueInputBuffer(inputBufferIndex, 0, m_yuv420.length, 0, 0);
}
/**编码完成后,取buffer,假如有,则添加头信息后准备发送*/
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = m_Encoder.dequeueOutputBuffer(bufferInfo, 0);
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);
if (m_Info != null) {
System.arraycopy(outData, 0, output, pos, outData.length);
pos += outData.length;
} else {
ByteBuffer spsPpsBuffer = ByteBuffer.wrap(outData);
if (spsPpsBuffer.getInt() == 0x00000001) {
m_Info = new byte[outData.length];
System.arraycopy(outData, 0, m_Info, 0, outData.length);
findSpsAndPps(m_Info);
//initDecoder();
}else {
return -1;
}
}
/**取完后要释放*/
m_Encoder.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = m_Encoder.dequeueOutputBuffer(bufferInfo, 0);
}
/**key frame 编码器生成关键帧时只有 00 00 00 01 65 没有pps sps, 要加上
* 一开始参考的代码是如此,新的N1所谓这段代码,但是老一点的板子,需要,而且不是 01 65
* 这个和编码器参数有关,有的是01 103这些,所以,下面的判断从output[4] = 65改成如下了*/
if(output[3] == 0x01)
{
System.arraycopy(output, 0, m_yuv420, 0, pos);
System.arraycopy(m_Info, 0, output, 0, m_Info.length);
System.arraycopy(m_yuv420, 0, output, m_Info.length, pos);
pos += m_Info.length;
}
return pos;
}
/**
* nv to Y420
* @param data
* @param ret
* @param width
* @param height
*/
public void nv21ToI420(byte[] data, byte[] ret,int width, int height) {
//byte[] ret = globalBuffer;
int total = width * height;
ByteBuffer bufferY = ByteBuffer.wrap(ret, 0, total);
ByteBuffer bufferU = ByteBuffer.wrap(ret, total, total / 4);
ByteBuffer bufferV = ByteBuffer.wrap(ret, total + total / 4, total / 4);
bufferY.put(data, 0, total);
for (int i=total; i<data.length; i+=2) {
bufferV.put(data[i]);
bufferU.put(data[i+1]);
}
}
/**
* 临时的函数,旋转Y420图像
* @param src
* @param des
* @param width
* @param height
*/
public static void RotateY420_90(byte[] src,byte[] des,int width,int height) {
int n = 0;
int hw = width / 2;
int hh = height / 2;
//copy y
for(int j = 0; j < width;j++)
{
for(int i = height - 1; i >= 0; i--)
{
des[n++] = src[width*i + j];
}
}
//copy u
for(int j = 0;j < hw;j++)
{
for(int i = hh - 1;i >= 0;i--)
{
des[n++] = src[width * height +hw * i + j];
}
}
//copy v
for(int j = 0; j < hw; j++)
{
for(int i = hh - 1;i >= 0;i--)
{
des[n++] = src[width * height + width * height/4 + hw * i + j];
}
}
}
/**
* 临时的函数,镜像Y420图像
* @param src
* @param des
* @param width
* @param height
*/
public static void FilpY420_horizontal(byte[] src,byte[] des,int width,int height) {
int n = 0;
int hw = width / 2;
int hh = height / 2;
//copy y
for(int j = 0; j < height; j++)
{
for(int i = width - 1;i >= 0;i--)
{
des[n++] = src[width * j + i];
}
}
//copy u
for(int j = 0; j < hh; j++)
{
for(int i = hw - 1;i >= 0;i--)
{
des[n++] = src[width * height + hw * j + i];
}
}
//copy v
for(int j = 0; j < hh; j++)
{
for(int i = hw - 1;i >= 0;i--)
{
des[n++] = src[width * height + hw * j + i];
}
}
}
private void findSpsAndPps(byte[] config) {
Log.e("tag", printBuffer(config,0,config.length-1));
int spsEnd = 1;
for(int i = 4; i < config.length-4; i ++){
if(config[i]==0X00 && config[i+1]==0X00 &&config[i+2]==0X00 &&config[i+3]==0X01){
spsEnd = i-1;
break;
}
}
Log.e("tag", spsEnd-3+"...............................");
m_sps = new byte[spsEnd-3];
m_pps = new byte[config.length-spsEnd-5];
System.arraycopy(config,4,m_sps,0,spsEnd-3);
System.arraycopy(config,spsEnd+5,m_pps,0,config.length-spsEnd-5);
}
protected static String printBuffer(byte[] buffer, int start,int end) {
String str = "";
for (int i=start;i<end;i++) str+=","+Integer.toHexString(buffer[i]&0xFF);
return str+"\r\n";
}
}
这里面有部分冗余,我没有完全修好。这里面几个要点,
第一个坑,最最重要的,ColorFormat,这个参数不对,就是一直错错错,而这个参数,非常恶心,因机器而已,我在网上翻阅各种代码,终于找到了一个Codec类的代码,这个代码的细节我暂时没看,效果是,列出了机器支持的多种颜色格式,但是这还没完,利用这个color模式,去配置MediaCodec,配置成功了,才认为他可用,不成功,就继续配(总有一个行的,都不行,是不是机器不支持硬编码了。。)。所以这部分找colorformat的代码比较乱,有几句代码也是莫名其妙,其实效果就是这几句,会报错,使得这个colorformat不支持硬件配置。
第二个坑,之前说了,camera过来的数据是90度倾斜的,实测,发现,不仅90度,还有一个镜像,因此去网上找了下,C++版本的Y420旋转,镜像代码,改成了Java(稍微动动),然后解决90度的问题。这里贴几个参考的链接:
http://www.zeroplace.cn/article.asp?id=984;http://blog.csdn.net/elesos/article/details/53220309;http://blog.csdn.net/chen495810242/article/details/39375443。另外NV21我也转成了Y420,其区别,雷神有讲(天妒英才啊):http://blog.csdn.net/leixiaohua1020/article/details/12234821
第三个坑是长和宽,因为旋转了,这俩参数需要变,所以编码器建立的时候,直接颠倒一下。
第四个坑也是神坑:就是关于H264中,sps,pps的,这部分,我代码助理稍微讲了些,新的板子,有没有这段话,都是可以解调,老的板子,就不行了,必须要有这部分。所以我还是加上了。sps,pps这部分代码,参考了http://bbs.csdn.net/topics/392039523这个作者提的问题,当然他的颜色问题,我也还是没解决。
最后是解码端,这段就没啥坑了,大部分人解码端出不来,其实是发端不匹配,发端出来的,写成H264给播放器,播放器能播,我觉得是因为播放器比较强大,而手机的解码端没这么厉害,适应不了太多(个人见解)
解码代码:
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.util.Log;
import android.view.Surface;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
/**
* 驿马类
* Created by 173.115 on 2017/8/17.
*/
public class Decoder {
/**控件类*/
/**解码显示的控件*/
private Surface m_surfaceView;
/**帧率*/
private int m_frameRate;
/**比特率*/
private int m_bitRate;
/**解码类*/
private MediaCodec m_Decoder;
/**解码帧顺序,累加型*/
private int mCount = 1;
/**线程控制*/
private boolean mIsTestingExit = true;
/**UDP Socket*/
private DatagramSocket m_datagramSocket;
/**
* 构造函数
* @param a_frameRate
* @param a_bitRate
* @param a_surfaceView
*/
public Decoder(int a_frameRate, int a_bitRate,Surface a_surfaceView) {
this.m_frameRate = a_frameRate;
this.m_bitRate = a_bitRate;
this.m_surfaceView = a_surfaceView;
this.mIsTestingExit =true;
initDecoder();
}
/**
* 设置解码类的参数
* 解码端参数不是特别苛刻和重要
* */
private void initDecoder() {
try {
m_Decoder = MediaCodec.createDecoderByType("video/avc");
} catch (IOException e) {
e.printStackTrace();
}
//MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", height, width);
/**264解码,以及长宽*/
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", GlobalConstant.VIDEOWIDTH, GlobalConstant.VIDEOHIGHT);
//mediaFormat.setByteBuffer("csd-0" , ByteBuffer.wrap(sps));
//mediaFormat.setByteBuffer("csd-1", ByteBuffer.wrap(pps));
/**设置最大输入size*/
mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, GlobalConstant.VIDEOWIDTH * GlobalConstant.VIDEOHIGHT);
/**设置比特率*/
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, m_bitRate);
/**设置帧率*/
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, m_frameRate);
/**设置关键帧*/
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
//mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
//mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,debugger.getEncoderColorFormat());
/**配置,以及设置显示的控件*/
m_Decoder.configure(mediaFormat, m_surfaceView, null, 0);
m_Decoder.start();
VideoPlay videoPlay = new VideoPlay();
videoPlay.start();
}
/**
* 喂数据
* @param buf
* @param offset
* @param length
*/
public void onFrame(byte[] buf, int offset, int length) {
try {
/**获取buffer*/
ByteBuffer[] inputBuffers = m_Decoder.getInputBuffers();
int inputBufferIndex = m_Decoder.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
/**把数据塞到buffer里,然后这个会自动传给解码*/
inputBuffer.put(buf, offset, length);
int capacity = inputBuffer.capacity();
Log.e("tag", "capacity < length?" + ":" + (capacity < length) + "......................");
//m_Decoder.queueInputBuffer(inputBufferIndex, 0, length, System.currentTimeMillis(), 0);
m_Decoder.queueInputBuffer(inputBufferIndex, 0, length, 10000 * mCount / 20, 0);
mCount++;
}
/**这步我感觉是取出解码后的数据,假如有,就播出来*/
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = m_Decoder.dequeueOutputBuffer(bufferInfo, 0);
Log.e("tag", outputBufferIndex + "onFrame.........................................................");
while (outputBufferIndex >= 0) {
/**释放缓冲区解码的数据到surfaceview,一般到了这一步,surfaceview上就有画面了*/
m_Decoder.releaseOutputBuffer(outputBufferIndex, true);
outputBufferIndex = m_Decoder.dequeueOutputBuffer(bufferInfo, 0);
}
} catch (IllegalStateException e) {
return;
} catch (NullPointerException e) {
return;
}
}
/**
* 关闭
*/
public void close() {
try {
mIsTestingExit = false;
m_Decoder.stop();
m_Decoder.release();
m_Decoder = null;
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*
*/
class VideoPlay extends Thread {
/**
* 这里有个疑问,H264出来的数据应该还是挺大的,起码一个以太网帧塞不下,本来想作顺序处理的,但是
* 尝试之后,是可以解调的,就没有做,这个byte8192,是测试之后取的一个值,这部分后续有研究价值
*/
public void run() {
try {
byte[] videobyte = new byte[8192];
m_datagramSocket = new DatagramSocket(GlobalConstant.VIDEO_UDPPORT);
/**这里设置5秒,因为视频聊天延迟会高一些,环境差的,可以设得再高*/
m_datagramSocket.setSoTimeout(5000);
while (mIsTestingExit == true) {
DatagramPacket dp = new DatagramPacket(videobyte,
videobyte.length);
m_datagramSocket.receive(dp);
if(dp.getLength()!= 0 && mIsTestingExit == true)
onFrame(dp.getData(), 0, dp.getLength());
}
if (m_datagramSocket != null) {
m_datagramSocket.close();
m_datagramSocket = null;
}
} catch (SocketException e) {
if (m_datagramSocket != null) {
m_datagramSocket.close();
m_datagramSocket = null;
}
} catch (SocketTimeoutException e) {
if (m_datagramSocket != null) {
m_datagramSocket.close();
m_datagramSocket = null;
}
} catch (IOException e) {
if (m_datagramSocket != null) {
m_datagramSocket.close();
m_datagramSocket = null;
}
}
}
}
}
这几个类,辅佐一些控制类代码,通信采用UDP(这里也有个坑,就是数据包长度,这个疑问我一直有,就是我是直接发送的,但是感觉包会过长,但是貌似底层帮我分割)。性能好的机器,没啥延时,性能差的,延迟有个0.5s-1s.另外,由于UDP不可靠以及代码发端不是很完善,偶尔也会有花屏。
8月21日更新,关于UDP这个问题,百度到了几个说法(由于IP能够发送或接收特定长度的数据报并不意味着接收应用程序可以读取该长度的数据。因此,UDP编程接口允许应用程序指定每次返回的最大字节数。如果接收到的数据报长度大于应用程序所能处理的长度,那么会发生什么情况呢?不幸的是,该问题的答案取决于编程接口和实现。
典型的Berkeley版socket API对数据报进行截断,并丢弃任何多余的数据。应用程序何时能够知道,则与版本有关(4.3BSD Reno及其后的版本可以通知应用程序数据报被截断)。
SVR4下的socket API(包括Solaris 2.x) 并不截断数据报。超出部分数据在后面的读取中返回。它也不通知应用程序从单个UDP数据报中多次进行读取操作。TLI API不丢弃数据。相反,它返回一个标志表明可以获得更多的数据,而应用程序后面的读操作将返回数据报的其余部分。在讨论TCP时,我们发现它为应用程序提供连续的字节流,而没有任何信息边界。TCP以应用程序读操作时所要求的长度来传送数据,因此,在这个接口下,不会发生数据丢失。)
最后一个没有解决的问题,就是,颜色感觉还是不太对劲。。解调出来的。。
在此做下记录,这个视频聊天前后研究也快2个礼拜了,ffmpeg这个坑,感觉得去补上。