基于RTMP推送实时AAC+H264流(一)

https://blog.csdn.net/scnu20142005027/article/details/56847293

从整体来看,推流端大概是这么个流程:采集、处理、编码、封装、推送 

如上图所示,图像采集线程和声音采集线程经过编码封装,将RTMP包写入到缓冲队列,发送线程从缓冲队列中读取RTMP包中并加上时间戳,然后送往RTMP服务器,由于一秒可能有上百个RTMP包,会造成大量的new和delete,所以实现了一个简单的内存池以减少内存分配及释放的次数

采集

分为图像和声音,采集图像用的是v4l2 API,采集声音用的是alsa API,这两个都是linux环境下的,暂时没有考虑其他环境

v4l2

流程:打开设备、检查设备能力、设置格式、设置缓冲区、读取缓冲队列 
详细代码位于V4L2Source.cpp,当然也可以看官方Demo 
打开设备:首先是打开设备文件,也可以用非阻塞的方式打开,但接下来的相关IO操作也是非阻塞的

fd = open("/dev/video0", O_RDWR);

检查设备能力:通过 V4L2_CAP_VIDEO_CAPTURE 位来判断设备是否具有捕获图像的能力

v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, &cap);
if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {return false;
}

设置格式:这一步之前可能需要检查一下摄像头支持的格式,检查需要用到ioctl函数和VIDIOC_ENUM_FMT参数,这里略过了这一步,统一设置成了支持较多的YUYV,也叫YUY2,然后编码前再把图片转为所需格式

v4l2_format fmt;fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_NONE;ioctl(fd, VIDIOC_S_FMT, &fmt);
if (fmt.fmt.pix.pixelformat != V4L2_PIX_FMT_YUV420) {return false;
}

设置缓冲区:缓冲区是一个队列,用于接收图像数据,这里用mmap的方式,将内核空间的内存映射到用户空间,这样可以避免拷贝,这里首先需要定义一个结构体来保存返回缓冲区的位置和大小

v4l2_requestbuffers reqbuf;
v4l2_buffer buf;reqbuf.count = 4;
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_REQBUFS, &reqbuf);for (int i = 0; i < mBufCount; ++i) {buf.index = i;buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;ioctl(fd, VIDIOC_QUERYBUF, &buf);mQueueBuf[i].length = buf.length;mQueueBuf[i].data = static_cast<char*>(mmap(0, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset));ioctl(fd, VIDIOC_QBUF, &buf);
}

读取缓冲队列:首先需要开始捕获图像,然后从缓冲队列中取出一个缓冲区,处理完之后需要放回缓冲队列

v4l2_buffer buf;
v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMON, &type);memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;while (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) {if (errno == EIO) continue;
}process(mQueueBuf[buf.index].data);ioctl(fd, VIDIOC_QBUF, &buf);

至此就完成了简单的图像采集,需要说明一下,所有贴出的代码,为了方便观看和说明,与项目中的相比,去掉了一些判断,做了一些修改

alsa

流程:打开设备、设置参数、读取数据 
详细代码位于PCMSource.cpp,开始之前可能需要了解一些相关知识,比如采样率,帧,通道,样本等,详情可以到网上搜索 
打开设备:打开设备并获取到句柄

snd_pcm_t *handle;
snd_pcm_open(&handle, "default", SND_PCM_STREAM_CAPTURE, 0);

设置参数:这里SND_PCM_ACCESS_RW_INTERLEAVED表示交错模式,也就是设置立体声时,数据格式为左声道样本和右声道样本交错存放,frames表示一周期内的帧数,比如按照这里的配置,一次获取的数据量为1024 * 2 * 16bit = 4096byte

int direct;
int rate = 44100;
snd_pcm_hw_params_t *param;
snd_pcm_uframes_t frames = 1024;snd_pcm_hw_params_alloca(&param);
snd_pcm_hw_params_any(handle, param);
snd_pcm_hw_params_set_access(handle, param, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_format(handle, param, SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_set_channels(handle, param, 2);
snd_pcm_hw_params_set_rate_near(handle, param, &rate, &direct);
snd_pcm_hw_params_set_period_size_near(handle, param, &frames, &direct);
snd_pcm_hw_params(handle, param);

读取数据:这里可能上溢,简单来说就是缓冲区中数据满了还没有被读取,这种情况调用snd_pcm_prepare重新准备设备,然后再次尝试读取

int ret;
while ((ret = snd_pcm_readi(handle, buf, frames)) < 0) {if (ret == -EPIPE) {// overrun, retry!snd_pcm_prepare(handle);} else if (ret < 0) {break;} else {process(ret, buf);}
}

这一步主要是对获取到的图像和声音数据进行处理,比如降噪,美颜什么的,由于没学过,不了解这块的知识,所以不知道怎么处理声音,图像也只是进行了简单地处理 
详细代码位于YUYVConverter.cppMotionDetector.cpp

YUYV2I420

由于x264编码用的baseline配置,所以编码需要I420格式的图像,如果无需动态检测,则可以直接把YUYV格式转为I420格式,前者是2个Y共用一对UV,后者是4个Y共用一对UV,除此之外还有存储格式上的不同,所以YUYV转换I420就是先把Y提取出来,然后隔行取一次UV,下面是个简单的例子

/*
4*4 YUYV packed
Y00 U00 Y01 V00 Y02 U01 Y03 V01
Y10 U10 Y11 V10 Y12 U11 Y13 V11
Y20 U20 Y21 V20 Y22 U21 Y23 V21
Y30 U30 Y31 V30 Y32 U31 Y33 V314*4 I420 planar
Y00 Y01 Y02 Y03
Y10 Y11 Y12 Y13
Y20 Y21 Y22 Y23
Y30 Y31 Y32 Y33
U00 U01 U20 U21
V00 V01 V20 V21
*/
const int length = width * height * 2;
const int stride = width * 2;char *pY = buf;
char *pU = pY + width * height;
char *pV = pU + (width * height / 4);
bool uFlags = true;for (int i = 0; i < length; ++i) {if (i % 2 == 0) {*(pY++) = data[i];} else if ((i / stride) % 2 == 0) {if (uFlags) {*(pU++) = data[i];} else {*(pV++) = data[i];}uFlags = !uFlags;}
}

YUYV2RGB24

因为动态检测用的是opencv库,需要将图像输入到cv::Mat这个类,但是我不知道怎么直接用cv::Mat存储YUYV图像,所以就先转换到了RGB格式,转换也不难,一组YUYV对应两组RGB,然后再用YUV的值算出RGB的值即可,这里我直接用的公式计算,涉及了较多的浮点运算,可能效率会比较低,有一种查表的方法,预先算出可能的值,牺牲小部分空间换取时间,由于YUV和RGB的取值范围都是[0, 255],所以可以用多个256*256的二维数组存下相应的值,此外,还要注意处理溢出的情况

const int length = width * height * 2;
char *p = buf;
int y1, y2, u, v;for (int i = 0; i < length; i += 4) {y1 = *(data++);u = *(data++);y2 = *(data++);v = *(data++);*(p++) = 1.164 * (y1 - 16) + 1.596 * (v - 128);*(p++) = 1.164 * (y1 - 16) - 0.391 * (u - 128) - 0.813 * (v - 128);*(p++) = 1.164 * (y1 - 16) + 2.018 * (u - 128);*(p++) = 1.164 * (y2 - 16) + 1.596 * (v - 128);     *(p++) = 1.164 * (y2 - 16) - 0.391 * (u - 128) - 0.813 * (v - 128);*(p++) = 1.164 * (y2 - 16) + 2.018 * (u - 128);
}

动态检测

用opencv库进行背景消除,将图像腐蚀膨胀,然后找出前景的轮廓,根据轮廓大小,判断是否需要在相应位置画出框框,最后还要把格式转换为I420

// cv::Ptr<cv::BackgroundSubtractor> sb = cv::createBackgroundSubtractorMOG2(500, 16, false);cv::Mat fg, rgb, yuv;
std::vector<std::vector<cv::Point>> contours;rgb.create(height, width, CV_8UC3);
memcpy(rgb.data, data, height * width * 3);sb->apply(rgb, fg);cv::erode(fg, fg, cv::Mat());
cv::dilate(fg, fg, cv::Mat());
cv::findContours(fg, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
drawRect(contours, rgb, 1000);
cv::cvtColor(rgb, yuv, CV_RGB2YUV_I420);

音视频开发(20)---基于RTMP推送实时AAC+H264流(一)相关推荐

  1. 音视频开发(22)---基于RTMP推送实时AAC+H264流(三)

    基于RTMP推送实时AAC+H264流(三) https://blog.csdn.net/scnu20142005027/article/details/60623670 推送 流程:初始化.连接服务 ...

  2. 音视频开发(21)---基于RTMP推送实时AAC+H264流(二)

    基于RTMP推送实时AAC+H264流(二) https://blog.csdn.net/scnu20142005027/article/details/57428107 编码 图像采用H264编码, ...

  3. 即时通讯音视频开发(十四):实时音视频数据传输协议介绍

    概述 随着移动互联网的快速发展以及智能终端性能的逐步提高,智能终端间进行实时音视频通讯成为移动互联网发展的一个重要方向.那么如何保证智能终端之间实时音视频数据通讯成为一个很现实的问题. 实际上,实时音 ...

  4. FFmpeg 音视频开发 20 年

    感谢小编邀请,让我写下 FFmpeg 20 年这么有历史厚重的话题. 写文章其实比录视频教程要求高很多,要字斟句酌,逻辑严密,理论知识严谨.由于个人文笔实在有限,长期以来,不敢随便写文章,更不敢出书, ...

  5. 深入了解音视频开发直播协议RTMP

    说起RTMP协议,相信很多人都比较陌生,这个协议相对HTTP.HTTPS.TCP等我们常见的协议而言,我们在工作中确实较少接触它,但是对现在如火如荼的直播行业,RTMP是一个重要的协议,它在实时音视频 ...

  6. 实时音视频开发理论必备:如何省流量?视频高度压缩背后的预测技术

    本文引用了"拍乐云Pano"的"深入浅出理解视频编解码技术"和"揭秘视频千倍压缩背后的技术原理之本文引用了"拍乐云Pano"的&q ...

  7. 音视频开发——音视频学习资料

    目录 1.为什么要学习音视频? 2.如何学习系统性音视频? 3.音视频相关的资料 最近有朋友问想学习音视频,应该怎么学,有什么资料吗? 这个问题也困扰我很久,几年前就想开始音视频相关的学习,但是一直找 ...

  8. 技术福利:最全实时音视频开发要用到的开源工程汇总

    [转自] https://my.oschina.net/jb2011/blog/1619628 1.前言 实时音视频的开发学习有很多可以参考的开源项目.一个实时音视频应用共包括几个环节:采集.编码.前 ...

  9. 即时通讯音视频开发(十八):详解音频编解码的原理、演进和应用选型

    1.引言 大家好,我是刘华平,从毕业到现在我一直在从事音视频领域相关工作,也有一些自己的创业项目,曾为早期Google Android SDK多媒体架构的构建作出贡献. 就音频而言,无论是算法多样性, ...

最新文章

  1. FineReport——权限分配以及自定义首页
  2. linux下vmware tools工具共享
  3. vue 文字上下循环滚动_基于 Vue 无缝滚动组件Vue-Seamless-Scroll
  4. Android系统自带样式(android:theme)(转)
  5. python3 hasattr getattr setattr delattr 对象属性 反射
  6. MySQL 使用explain查看执行计划
  7. Linux Centos7 下安装Mysql - 8.0.15
  8. Python 全局变量和局部变量迷惑人的小示例
  9. html文件头部固定代码
  10. OpenCASCADE:拓扑 API之3D模型Defeaturing
  11. MySQL 8.0.22执行器源码分析HashJoin —— 一些初始化函数的细节步骤
  12. Pagination(分页) 从前台到后端总结
  13. ros 发布信息频率_ROS 消息发布器和订阅器Publisher, Subscriber
  14. Win10 下Visual Studio 2017源码编译Paddle
  15. python动态导入类或函数_Python 动态从文件中导入类或函数的方法
  16. oracle not like优化,oracle的like优化,对比了一下,效果确实比like好些。
  17. JAVA缓存机制浅析
  18. java小游戏实战局域网联机_结对编程3——黄金点小游戏实现局域网联机
  19. 矩阵标准型的系数是特征值吗_「线性代数」根据特征值,将二次型化为标准形、规范形...
  20. mybatisplus-代码级别的自动生成创建丶更新时间

热门文章

  1. 数据结构之栈与递归的实现及应用(斐波拉契数列递归解法和strlen递归解法)
  2. python + opencv: 解决不能读取视频的问题
  3. java 文件url地址_简单的解析文件,取URL地址,并根据地址抓下页面
  4. java中jnum i .length,java数组和多维数组
  5. jsp springmvc 视图解析器_SpringMVC工作原理
  6. Linux 大文件拷贝失败,linux – Rsync失败,“文件太大”
  7. python sys os_python常用的一些东西——sys、os等(转)
  8. 今天需要修复的bug
  9. css中hover的妙用!!
  10. @getMapping与@postMapping