赞
踩
工作中用到IPCamera支持双摄(即一个IPCamera带两个摄像头),IPC端将两个摄像头的画面上下拼接成了一个画面发布dash到云端,并且携带SEI信息。SEI信息中带两个frame(x, y, width, height),app端(iOS、安卓)根据这个信息拆分画面通过opengl展示到两个view上,以便以不同的排列方式展示双摄画面。
ffplay+SDL2+opengles在iOS中使用(参考ijkplayer)_ffplay swift ios-CSDN博客
- //根据视频获取的AVPacket获取SEI中的双摄画面信息,以便根据这些信息拆分显示双摄。
- void FSPlay::ffp_handleSEI(AVPacket *pkt) {
- //如果不需要查询SEI信息(单摄不需要查询)或multi数据已经获取过了(仅获取一次即可),则直接返回。
- if (!needSearchSEI || multi.acquired) {
- return;
- }
-
- //将AVPacket的data、size数据传入sei_saas_data_without_nal_unit方法获取实际的SEI buf,内部通过查询前后256byte的数据查找对应的uuid,找到uuid后其后边就是SEI对应的数据。
- //NAL引导码 + NAL帧类型(SEI) + SEI帧类型(用户自定义类型) + 数据长度 + UUID + 净荷数据 + 0X80
- //引导码为0x00 0x00 0x00 0x01或者 0x00 0x00 0x01,NAL帧类型为6,SEI帧类型为5,数据长度为UUID长度+ 净荷数据长度 0x80为尾部固定。
- fsbase::ByteBuf buf = fsbase::sei_saas_data_without_nal_unit(pkt->data, pkt->size);
- if (buf.size() > 0) {
- fsbase::sei_frame_t f;
-
- //通过SEI的buf data创建sei_frame结构体。
- fsbase::sei_frame_make(buf.data(), 0, &f);
-
- //如果saas_data有效时执行if代码。
- if (f.saas_data != NULL && f.saas_len > 0) {
-
- //根据saas_data构建multi结构体。
- std::shared_ptr<fsbase::multi_rect_t> multi_cpp = fsbase::sei_query_multi_rect(f.saas_data, f.saas_len);
-
- //如果multi_cpp有效时执行if代码。
- if (multi_cpp != NULL) {
-
- //如果count数量为2的时候说明是对的,继续执行if
- if (multi_cpp->count + 1 >= 2) {
-
- //将acquired设置为1表示已经获取过multi数据了,后续可以直接使用,不需要重复获取了。
- multi.acquired = 1;
-
- //从multi_cpp中读取rects[0]的x,y,width,height数据保存到成员变量multi中。
- multi.primary = FSFrameRange();
- multi.primary.x = multi_cpp->rects[0].x;
- multi.primary.y = multi_cpp->rects[0].y;
- multi.primary.width = multi_cpp->rects[0].w;
- multi.primary.height = multi_cpp->rects[0].h;
-
- //从multi_cpp中读取rects[1]的x,y,width,height数据保存到成员变量multi中。
- multi.secondary = FSFrameRange();
- multi.secondary.x = multi_cpp->rects[1].x;
- multi.secondary.y = multi_cpp->rects[1].y;
- multi.secondary.width = multi_cpp->rects[1].w;
- multi.secondary.height = multi_cpp->rects[1].h;
- }
- }
- }
- }
- }
- int FSPlay::read_thread(void *arg) {
- //...省略代码
- if (pkt->stream_index == is->video_stream && pkt_in_play_range
- && !(is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) {
- packet_queue_put(&is->videoq, pkt);
-
- //调用ffp_handleSEI读取SEI信息到multi
- ffp_handleSEI(pkt);
- }
- //...省略代码
- }
5. video_image_display方法中根据multi的primary和secondary将原rgb数据拆分成两个画面分别回调给opengl端显示。
- void FSPlay::video_image_display(VideoState *is)
- {
- Frame *vp;
-
- vp = frame_queue_peek_last(&is->pictq);
-
- if (rgbFrame == NULL) {
- rgbFrame = av_frame_alloc();
- }
- av_image_alloc(rgbFrame->data, rgbFrame->linesize, vp->width, vp->height, AV_PIX_FMT_RGB24, 1);
-
- enum AVPixelFormat sw_pix_fmt = (enum AVPixelFormat)(vp->format);
-
- swsContext = sws_getContext(vp->width, vp->height, sw_pix_fmt, vp->width, vp->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL);
-
- SDL_LockMutex(is->pictq.mutex);
- sws_scale(swsContext, vp->frame->data, vp->frame->linesize, 0, vp->frame->height, rgbFrame->data, rgbFrame->linesize);
- SDL_UnlockMutex(is->pictq.mutex);
-
- //如果不需要查询SEI信息(单摄不需要查询,或者multi没有获得时走单摄的显示逻辑。
- if (!needSearchSEI || !multi.acquired) {
- VideoFrame *videoFrame = (VideoFrame *)malloc(sizeof(VideoFrame));
- videoFrame->width = vp->width;
- videoFrame->height = vp->height;
- videoFrame->planar = 1;
- videoFrame->pixels[0] = (uint8_t *)malloc(vp->width * vp->height * 3);
-
- videoFrame->format = AV_PIX_FMT_RGB24;
- copyFrameData(videoFrame, rgbFrame);
- if (renderCallback != NULL && openglesView != NULL) {
- renderCallback(openglesView, videoFrame);
- }
- free(videoFrame->pixels[0]);
- free(videoFrame);
- } else {
- //创建destination1用来放主摄的rgb数据。
- VideoFrame *destination1 = (VideoFrame *)malloc(sizeof(VideoFrame));
- destination1->width = multi.primary.width;
- destination1->height = multi.primary.height;
- destination1->planar = 1;
-
- //根据目标主摄尺寸分配内存buf。
- destination1->pixels[0] = (uint8_t *)malloc(destination1->width * destination1->height * 3);
- destination1->format = AV_PIX_FMT_RGB24;
-
- //根据multi.primary的x,y,width,height拷贝数据从rgbFrame到destination1->pixels[0]中。
- copyFrameData(destination1, rgbFrame, &(multi.primary));
-
- //通过renderCallback将主摄的显示view和画面数据回调给opengl端进行绘制。
- if (renderCallback != NULL && openglesView != NULL) {
- renderCallback(openglesView, destination1);
- }
-
- //释放资源。
- free(destination1->pixels[0]);
- free(destination1);
-
- //创建destination2用来放次摄的rgb数据。
- VideoFrame *destination2 = (VideoFrame *)malloc(sizeof(VideoFrame));
- destination2->width = multi.secondary.width;
- destination2->height = multi.secondary.height;
- destination2->planar = 1;
-
- //根据目标次摄尺寸分配内存buf。
- destination2->pixels[0] = (uint8_t *)malloc(destination2->width * destination2->height * 3);
- destination2->format = AV_PIX_FMT_RGB24;
-
- //根据multi.secondary的x,y,width,height拷贝数据从rgbFrame到destination2->pixels[0]中。
- copyFrameData(destination2, rgbFrame, &(multi.secondary));
-
- //通过renderSecondCallback将次摄的显示view和画面数据回调给opengl端进行绘制。
- if (renderSecondCallback != NULL && renderSecondView != NULL) {
- renderSecondCallback(renderSecondView, destination2);
- }
-
- //释放资源。
- free(destination2->pixels[0]);
- free(destination2);
- }
-
- av_freep(&rgbFrame->data[0]);
- sws_freeContext(swsContext);
- swsContext = NULL;
- }
6. copyFrameData方法用于将原rgb数据以指定的range拷贝到目标Frame中。
- //将source中的数据根据range标识的x,y,width,height拷贝到destination中
- void FSPlay::copyFrameData(VideoFrame *destination, AVFrame *source, FSFrameRange *range) {
- //获取原始数据指针
- uint8_t *src = source->data[0];
-
- //获取目标数据指针
- uint8_t *dst = destination->pixels[0];
-
- //获取linesize,src每次换行时通过linesize进行偏移。
- int linesize = source->linesize[0];
-
- //拿到目标的宽高,width为byte数,height为循环次数
- int width = destination->width * 3;
- int height = destination->height;
-
- //重置内存为0
- memset(dst, 0, width * height);
-
- //将src指针偏移到需要拷贝的首行
- src += linesize * range->y;
-
- //遍历height次。
- for (int i = 0; i < height; ++i) {
- //拷贝单行数据,从src偏移x * 3开始拷贝,共拷贝width长度。
- memcpy(dst, src + range->x * 3, width);
-
- //目标指针偏移一行
- dst += width;
-
- //src指针偏移一行
- src += linesize;
- }
- }
7. sei_saas_data_without_nal_unit方法用于根据uuid去搜索SEI信息,搜索前256字节和后256字节。
- ByteBuf sei_saas_data_without_nal_unit(const uint8_t *buf, int size) {
- /* 先搜索前IV_SEI_PROBE_SIZE字节 */
- int index = 0;
- int end = MIN(IV_SEI_PROBE_SIZE, size);
-
- __SEARCH__:
- while (index < end) {
- auto byteBuf = search_sei_data_by_uuid(buf + index, size - index);
- if (byteBuf.size() == 0) {
- index = end;
- break;
- }
- return byteBuf;
- }
-
- /* 若后面还有数据,再搜索后IV_SEI_PROBE_SIZE字节 */
- if (index < size) {
- index = MAX(index, size - IV_SEI_PROBE_SIZE);
- end = size;
- goto __SEARCH__;
- }
-
- return ByteBuf();
- }
-
- /**
- 跟据UUID搜索自定义SEIData数据位置
-
- - Parameters:
- - p: 搜索起始地址
- - size: 搜索区间长度
- - sei_buf: 查找到的SEIData数据位置, IV_SEI_UUID[]开头
- @return 返回SEIData数据长度
- */
- static ByteBuf search_sei_data_by_uuid(const uint8_t *p, int size) {
- int i = 2;
-
- while (size - i > IV_SEI_UUID_LEN) {
- // 检查是哪个版本的SEI协议
- for (int v = 0; v < sizeof(SEI_UUIDs) / sizeof(SEI_UUIDs[0]); v++) {
- auto &uuid = SEI_UUIDs[v];
- // a.(低成本)初步匹配前4字节
- // b.(高成本)校验UUID是否全匹配T平台协议
- if (CHECK_FIRST_4BYTES_EQUAL(p+i, uuid) && memcmp(p + i + 4, &uuid[4], IV_SEI_UUID_LEN-4) == 0){
- int k = i - 1;
- // c. 获取净荷长度, 往后累加数值直到不是0xFF后为止,累加的数值作为数据长度
- int payloadLen = p[k];
- while (k > 0 && p[--k] == 0xFF) {
- payloadLen += 0xFF;
- }
-
- ByteBuf res;
- if (v == 0) {
- res = ByteBuf(p + i, p + i + payloadLen);
- } else {
- res = remove_redundant_bytes(p + i, payloadLen);
- }
-
- // d. SEI类型值是用户自定义的固定为0x05
- // e. 校验SEI帧结尾是否为0x80
- if (p[k ] == IV_SEI_USER_DATA &&
- p[i + payloadLen] == IV_SEI_DATA_END) {
- return res;
- }
- }
- }
-
- i++;
- }
- return ByteBuf();
- }
8. 通过renderCallback和renderSecondCallback回调的画面数据,用opengles进行渲染,显示到两个对应的view上。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。