9.CameraX采集数据h264和faac推流

CameraX采集数据生成 YUV_420_888格式

通过分析接口得到ImageProxy 然后得到planes数组

 @Override
public void analyze(ImageProxy image, int rotationDegrees) { 
  int width = image.getWidth();
  int height = image.getHeight();
  //格式 YUV/RGB...
  int format = image.getFormat();
  //图像数据
  ImageProxy.PlaneProxy[] planes = image.getPlanes();
  //数组3个元素  Y U V
}

I420的排列 YYYY YYYY YYYY YYYY UUUU VVVV

对于U和V数据 排列方式可能有两种

1. planes[1] = {uuuu...}  planes[2] = {vvvv...}  

2. planes[1] = {uvuv...}  planes[2] = {vuvu...}

通过 int pixelstride = plane.getPixelStride()获取返回值 0 表示上述第一种情况 返回值1 表示上述第二种情况

YUV数据获取 需要考虑RowStride 步长问题(字节对齐)

Y数据

1) 若RowStride = width  直接通过planes[0].getBuffer获取Y数据
2) 若RowStride > width  如4*4的I420每行以8字节对齐  还需要考虑最后一行无占位数据
//用于保存获取的I420数据。大小为:y+u+v, width*height + width/2*height/2 + width/2*height/2 ByteBuffer yuv420 = 
ByteBuffer.allocate(image.getWidth() * image.getHeight() * 3 / 2);

int rowStride = plane.getRowStride();
ByteBuffer buffer = plane.getBuffer();
byte[] row = new byte[image.getWidth()];

// 每行要排除的无效数据,但是需要注意:实际测试中 最后一行没有这个补位数据 
// 因为Y数据 RowStride 为大于等于Width,所以不会出现负数导致错误
// RowStride 等于Width,则得到空数组,不丢弃数据
byte[] skipRow = new byte[rowStride - image.getWidth()]; 
for (int i = 0; i < image.getHeight(); i++) {
    buffer.get(row); 
    yuv420.put(row);
    // 不是最后一行,则丢弃此数据
    if (i < image.getHeight() - 1) {
        buffer.get(skipRow); 
    }
}
= width
> width

U与V数据

1) = width
2) > width
3) = width/2
4) > width/2

1)planes[1]中不仅包含U数据,还会包含V的数据,此时pixelStride==2

planes[1]


= width

planes[2]

= width

2)Y数据一样,可能由于字节对齐,出现RowStride大于Width的情况,与等于Width一样, planes[1]中不仅包含U 数据,还会包含V的数据,此时pixelStride==2

planes[1]

> width

planes[2]

> width

3)获取的U数据对应的RowStride等于Width/2,表示我们得到的planes[1]只包含U数据。此时pixelStride==1

= width/2

4)planes[1]只包含U数据,但是与Y数据一样,可能存在占位数据。此时pixelStride==1

> width/2

获得了摄像头采集的数据之后,我们需要获取对应的YUV数据,需要根据pixelStride判断格式,同时还需要通过 rowStride来确定是否存在无效数据,那么最终我们获取YUV数据的完整实现为

 //图像格式
        int format = image.getFormat();
        if (format != ImageFormat.YUV_420_888) {
            //抛出异常
        }

        ByteBuffer i420 = ByteBuffer.allocate(image.getWidth() * image.getHeight() * 3 / 2);
        // 3个元素 0:Y,1:U,2:V
        ImageProxy.PlaneProxy[] planes = image.getPlanes();
        // byte[]

        /**
         * Y数据
         */
        //y数据的这个值只能是:1
        int pixelStride = planes[0].getPixelStride();
        ByteBuffer yBuffer = planes[0].getBuffer();
        int rowStride = planes[0].getRowStride();

        //1、rowStride 等于Width ,那么就是一个空数组
        //2、rowStride 大于Width ,那么就是每行多出来的数据大小个byte
        byte[] skipRow = new byte[rowStride - image.getWidth()];
        byte[] row = new byte[image.getWidth()];
        for (int i = 0; i < image.getHeight(); i++) {
            yBuffer.get(row);
            i420.put(row);
            // 不是最后一行才有无效占位数据,最后一行因为后面跟着U 数据,没有无效占位数据,不需要丢弃
            if (i < image.getHeight() - 1) {
                yBuffer.get(skipRow);
            }
        }

        /**
         * U、V
         */
        for (int i = 1; i < 3; i++) {
            ImageProxy.PlaneProxy plane = planes[i];
            pixelStride = plane.getPixelStride();
            rowStride = plane.getRowStride();
            ByteBuffer buffer = plane.getBuffer();

            //每次处理一行数据
            int uvWidth = image.getWidth() / 2;
            int uvHeight = image.getHeight() / 2;

            // 一次处理一个字节
            for (int j = 0; j < uvHeight; j++) {
                for (int k = 0; k < rowStride; k++) {
                    //最后一行
                    if (j == uvHeight - 1) {
                        //uv没混合在一起
                        if (pixelStride == 1) {
                            //rowStride :大于等于Width/2
                            // 结合外面的if:
                            //  如果是最后一行,我们就不管结尾的占位数据了
                            if (k >= uvWidth) {
                                break;
                            }
                        } else if (pixelStride == 2) {
                            //uv混在了一起
                            // rowStride:大于等于 Width
                            if (k >= image.getWidth()) {
                                break;
                            }
                        }
                    }


                    byte b = buffer.get();
                    // uv没有混合在一起
                    if (pixelStride == 1) {
                        if (k < uvWidth) {
                            i420.put(b);
                        }
                    } else if (pixelStride == 2) {
                        // uv混合在一起了
                        //1、偶数位下标的数据是我们本次要获得的U/V数据
                        //2、占位无效数据要丢弃,不保存
                        if (k < image.getWidth() && k % 2 == 0) {
                            i420.put(b);
                        }
                    }
                }
            }
        }


        //I420
        byte[] result = i420.array();

旋转和缩放 可以使用openCV 或者 libyuv实现

image.png

视频编码和推流 使用h264

x264_encoder_encode(...);//编码的i_pts每次需要增长

H.264码流在网络中传输时实际是以NALU的形式进行传输的, 每个NAL之间由00 00 00 01 或者 00 00 01 进行分割

在分割符之后的第一个字节,就是表示这个nal的类型

  • 0x67:sps
  • 0x68: pps
  • 0x65: IDR
for (int i = 0; i < pi_nal; ++i) {
    int type = pp_nal[i].i_type;
    uint8_t *p_payload = pp_nal[i].p_payload;//数据
    int i_payload = pp_nal[i].i_payload;//数据长度
    if(type == NAL_SPS){
        spslen = i_payload - 4;//去掉间隔 00 00 00 01
        sps=(uint8_t)alloca(spslen);//在栈中申请内存使用完自动释放
        memcpy(sps,p_payload+4,spslen);
    }else if(type == NAL_PPS){
        ppslen = i_payload - 4;
        pps=(uint8_t)alloca(ppslen);
        memcpy(pps,p_payload+4,ppslen);
        //发送数据 sendSPSPPS(sps, spslen, pps, ppslen);
    }else{
        //发送h264 sendH264(type, p_payload, i_payload);
    }
}

音频编码和推流 faac

//输入样本  要给编码器编码的样本数
unsigned long inputSample;
unsigned long maxOutputBytes;
codec = faacEncOpen(sampleRate,channels,&inputSample,&maxOutputBytes);
inputByteNum = inputSample*2; //样本是16位  一个样本是2字节
//得到编码的各种参数配置
faacEncConfigurationPtr configurationPtr = faacEncGetCurrentConfiguration(codec);
configurationPtr->mpegVersion = MPEG4;
configurationPtr->aacObjectType=LOW;
configurationPtr->outputFormat=0;//传入0表示编码出aac裸数据 传1表示每一帧音频编码结果数据都会携带ADTS(包含采样率 声道等信息的数据头)
configurationPtr->inputFormat=FAAC_INPUT_16BIT;
faacEncSetConfiguration(codec,configurationPtr);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,695评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,569评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,130评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,648评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,655评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,268评论 1 309
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,835评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,740评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,286评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,375评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,505评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,185评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,873评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,357评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,466评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,921评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,515评论 2 359

推荐阅读更多精彩内容