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

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

编码

图像采用H264编码,声音采用AAC编码,用的是x264和faac这两个库

x264

流程:配置编码器、获取元数据、编码 
配置编码器:首先是使用预先设定好的配置ultrafastzerolatency,这两个用来控制编码速度和质量,也就是代替我们修改了一些参数,baseline同理,也是预配置,然后是设置高宽,帧率等参数,接下来是几个重要的点: 
i_keyint_max,代表最大关键帧间隔,默认值是250,也就是如果帧率为25的话,每10秒发送一次关键帧,间隔太过长了,比如视频推流经过1秒后,我打开播放器从服务器获取媒体流,这时候由于没有关键帧,无法显示出画面,也就是还要等9秒才能看到画面,所以这里设置成了和fps一样,也就是最多一秒要发一次关键帧 
i_rc_method,代表码率控制方式,X264_RC_ABR为平均码率 
b_repeat_headers,代表是否要在关键帧前加入sps和pps,这两个帧在解码的时候需要用到,所以一般要在每个关键帧前加入,防止半途播放的用户无法解码,然而这里关闭了这个选项,是由于返回的数据格式不方便封装成RTMP包,而且sps和pps一般是不变的,所以,采取的策略是推流前获取这两个帧,然后保存下封装好的RTMP包,在每次发送数据时判断如果需要发送的是关键帧就先发送sps和pps 
b_annexb,代表是否用附录B的格式打包NAL单元,简单来说就是在NAL单元前加上00 00 00 0100 00 01用以分割各个单元,相对应的是RTP格式,这种格式在NAL单元前加上四个表示单元长度的字节,考虑到RTMP的封装格式与RTP格式一样,所以这里关闭这个选项

x264_param_t param;
x264_picture_t picture;
x264_t *handle;x264_param_default_preset(&param, "ultrafast", "zerolatency");
x264_param_apply_profile(&param, "baseline");param.i_log_level = X264_LOG_NONE;
param.i_csp = X264_CSP_I420;
param.i_width = width;
param.i_height = height;
param.i_fps_den = 1;
param.i_fps_num = fps;
param.i_keyint_max = fps;param.rc.i_rc_method = X264_RC_ABR;
param.rc.i_bitrate = bitrate;param.b_repeat_headers = 0;
param.b_annexb = 0;x264_picture_alloc(&picture, param.i_csp, param.i_width, param.i_height);handle = x264_encoder_open(&param);

获取元数据:也就是之前提到的sps和pps,这里264_encoder_headers返回的应该有三个NAL单元,除了sps和pps,还有一个sei,代表的是增强信息,好像是辅助解码的一些额外参数信息,没有也不影响正常解码,所以这里忽略了

x264_nal_t *nal;
int temp, size; x264_encoder_headers(handle, &nal, &temp);
size = nal[0].i_payload + nal[1].i_payload;process(size, nal->p_payload);

编码:把I420格式的亮度与色度分别拷贝到picture内,这里i_pts控制显示顺序,由于一帧会被分为多个slice,所以x264_encoder_encode生成一组x264_nal_t,这里第三个参数代表的是生成的x264_nal_t数量,也就是说nal[1]代表第二个NAL单元,需要注意的是,这些x264_nal_tp_payload成员内存上是连续的,且函数的返回值表示的是所有p_payload的总字节数,这里我们用到的就是p_payload与其总字节数

const int luma = height * width;
const int chroma = luma / 4;
int temp, size;
x264_picture_t out;
x264_nal_t *nal;memcpy(picture.img.plane[0], frame, luma);
memcpy(picture.img.plane[1], frame + luma, chorma);
memcpy(picture.img.plane[2], frame + luma + chorma, chorma);picture.i_pts = pts++;size = x264_encoder_encode(handle, &nal, &temp, &picture, &out);process(size, nal->p_payload);

faac

流程:配置编码器、获取元数据、编码 
配置编码器:设置采样率和通道数,打开编码器,获取到最大可接受的样本数量和输出的字节大小,以此来决定缓冲区的大小,接下来是几个参数,有些参数不懂什么意思 
inputFormat,代表输入格式,这里选的是FAAC_INPUT_16BIT,也就是一个样本的大小为16位,与之前采集时设置的相同 
outputFormat,代表输出格式,值为1表示adts格式 
aacObjectType,代表AAC规格,LOW也就是Low Complexity,由于少了预测和增益控制,编码复杂度较低,因此编码效率更高 
useLfe,代表使用低频增强,具体作用不知道

unsigned long maxSample, bufLength;
char *buf;encoder = faacEncOpen(sampleRate, channals, &maxSample, &bufLength);faacEncConfigurationPtr conf = faacEncGetCurrentConfiguration(encoder);
conf->inputFormat = FAAC_INPUT_16BIT;
conf->outputFormat = 1;
conf->aacObjectType = LOW;
conf->allowMidside = 0;
conf->useLfe = 0;
conf->bitRate = bitrate;
conf->bandWidth = 0.5 * bitrate;
faacEncSetConfiguration(encoder, conf);  buf = new char[bufLength];

获取元数据:结果应该只有2个字节,不调用这个函数,自己按照规范构造也可以

unsigned char *buf;
unsigned long size;faacEncGetDecoderSpecificInfo(encoder, &buf, &size);process(size, buf);

编码:需要注意,一开始编码的几个帧都不返回数据,所以封装的时候需要额外判断一下,还有就是这里的sample如果和maxSample不一致的话,编码出来的声音会很奇怪

int size = faacEncEncode(encoder, (int*)(data), sample, buf, bufLength);process(size, buf);

封装

所有的消息块都需要预留一个RTMP_MAX_HEADER_SIZE的大小来存放块头,且元数据与普通数据需要分开处理,时间戳在发送时再设置

H264

元数据:封装时要求sps与pps的大小用2个字节来表示,而编码得到的是用4个字节来表示,所以需要拷贝时需要注意下偏移,这里包括后面的m_nChannel = 0x04代表声音和视频通道

char *buf = new char[RTMP_MAX_HEADER_SIZE + 8 + length];
char *body = buf + RTMP_MAX_HEADER_SIZE;RTMPPacket packet;
packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
packet.m_packetType = RTMP_PACKET_TYPE_VIDEO;
packet.m_nChannel = 0x04;
packet.m_hasAbsTimestamp = 0;
packet.m_nBodySize = 8 + length;
packet.m_body = body;*(body++) = 0x17; // 1-keyframe, 7-AVC
*(body++) = 0x00;
*(body++) = 0x00;
*(body++) = 0x00;
*(body++) = 0x00;// AVCDecoderConfigurationRecord*(body++) = 0x01; // configurationVersion
*(body++) = data[5]; // AVCProfileIndication
*(body++) = data[6]; // profile_compatibility
*(body++) = data[7]; // AVCLevelIndication
*(body++) = 0xff; // 111111(reserved) + lengthSizeMinusOneint len = (data[2] << 8) | data[3];*(body++) = 0xe1; // 111(reserved) + numOfSequenceParameterSets
memcpy(body, data + 2, len + 2);body += (len + 2);
*(body++) = 0x01; // numOfPictureParameterSets
memcpy(body, data + len + 6, length - len - 6);

普通数据:第一个字节根据是否为关键帧而取不同的值

char *buf = new char[RTMP_MAX_HEADER_SIZE + length + 5];
char *body = buf + RTMP_MAX_HEADER_SIZE;RTMPPacket packet;
packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
packet.m_packetType = RTMP_PACKET_TYPE_VIDEO;
packet.m_nChannel = 0x04;
packet.m_hasAbsTimestamp = 0;
packet.m_nBodySize = length + 5;
packet.m_body = body;*(body++) = (data[4] & 0x1f) == 0x05 ? 0x17 : 0x27;
*(body++) = 0x01;
*(body++) = 0x00;
*(body++) = 0x00;
*(body++) = 0x00;
memcpy(body, data, length);

AAC

元数据:0xAF代表AAC数据

char *buf = new char[RTMP_MAX_HEADER_SIZE + length + 2];
char *body = buf + RTMP_MAX_HEADER_SIZE;RTMPPacket packet;
packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
packet.m_packetType = RTMP_PACKET_TYPE_AUDIO;
packet.m_nChannel = 0x04;
packet.m_hasAbsTimestamp = 0;
packet.m_nBodySize = length + 2;
packet.m_body = body;*(body++) = 0xAF;
*(body++) = 0x00;
memcpy(body, data, length);

普通数据:AAC数据的前7个字节为帧分隔符,不需要封装在内

char *buf = new char[RTMP_MAX_HEADER_SIZE + length - 5];
char *body = buf + RTMP_MAX_HEADER_SIZE;RTMPPacket packet;
packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM;
packet.m_packetType = RTMP_PACKET_TYPE_AUDIO;
packet.m_nChannel = 0x04;
packet.m_hasAbsTimestamp = 0;
packet.m_nBodySize = length - 5;
packet.m_body = body;*(body++) = 0xAF;
*(body++) = 0x01;
memcpy(body, data + 7, length - 7);

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

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

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

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

    基于RTMP推送实时AAC+H264流(一) https://blog.csdn.net/scnu20142005027/article/details/56847293 从整体来看,推流端大概是这么 ...

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

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

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

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

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

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

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

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

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

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

  8. 音视频开发常用分析工具介绍

    综述 工欲善其事,必先利其器:兵马未到,粮草先行. 在音视频开发过程中,利用工具可以更方便.更直观.更快捷的分析音视频的数据,便于开发过程中分析.调试和解决问题. 现总结一些音视频开发过程中常用的分析 ...

  9. 如何测试你的即时通讯实时音视频开发方案

    真正了解过实时音视频开发的同行都知道,实时音视频开发所需的技术储备和技能要求都是比较高的,当我们历尽折腾,自已捣鼓出的方案出声出图后,离产品化还有多远呢?为了避免出现测试不充分盲目上线导致用户体验不佳 ...

最新文章

  1. 2017年全球光伏需求有望首次突破100吉瓦
  2. 当深度学习搭上一双鞋,有人要用这检测你的压力水平!可无线操作,准确率达84%...
  3. struts标签判断两个session中的字符串值是否相等
  4. C/C++:Windows cmd 指令
  5. spark-jar冲突解决方案
  6. stability condition in queueing system
  7. 【英语学习】【WOTD】accolade 释义/词源/示例
  8. 全国计算机二级c语言和江苏教材一样吗,计算机二级省级和全国计算机二级考试内容一样吗...
  9. PD连接远程mysql_PowerDesigner连接远程Oracle数据库 | 学步园
  10. Java中常用的正则表达式判断,如IP地址、电话号码、邮箱等
  11. VBScript函数
  12. java pdf 转tif_JAVA中 PDF文件转成TIFF文件的2种方式
  13. 基于selenium的python浏览器脚本制作教程
  14. 云痕大数据 家长登录_云痕大数据查成绩app
  15. 使用LL库开发STM32:UART基础使用
  16. 感谢《蜗居》中的100句经典台词让我们提前认清了现实[转帖]
  17. spark写入Oracle 报错 java.lang.ArrayIndexOutOfBoundsException: -32423
  18. 短线股票怎么操作怎样才能炒好短线股票
  19. 5教程 watchout_初中英语阅读课教学交流
  20. 渡一教育公开课web前端开发JavaScript精英课学习笔记(三)条件语句,循环语句

热门文章

  1. 计算机常发故障英语,vipkid英语常见问题解决办法
  2. oracle宕机原因排查,oracle不定期的出现宕机的问题诊断
  3. python 字符串赋值操作(分别使用三 种分隔符),Python学习笔记(3)字符串,python,三...
  4. c语言if语句怎么表达字符,C语言if语句的基本用法
  5. php的函数是谁写的,一个用PHP写的中文分词函数
  6. ucore和linux区别,附录 - 附录A—ucore历史 - 《操作系统的基本原理与简单实现》 - 书栈网 · BookStack...
  7. python中lastch_python复习笔记
  8. 【重难点】【JUC 03】怎么实现一个线程安全的队列、手写模拟实现一个阻塞队列
  9. 力扣53.最大子序和 多种方法
  10. CSRF跨站请求伪造攻击